Skip to content

feat(risk): compute tier 1 in the script, floor on security paths, degrade instead of drop, keep history - #1245

Open
guyoron1 wants to merge 3 commits into
fullsend-ai:mainfrom
guyoron1:feat/risk-score-hardening
Open

feat(risk): compute tier 1 in the script, floor on security paths, degrade instead of drop, keep history#1245
guyoron1 wants to merge 3 commits into
fullsend-ai:mainfrom
guyoron1:feat/risk-score-hardening

Conversation

@guyoron1

@guyoron1 guyoron1 commented Sep 10, 2026

Copy link
Copy Markdown

ADR 0089 leaves tier 1 to risk-tier1.sh but has the sub-agent re-emit it, which is how the same PR scored 1, 2, 1 across three re-reviews (#1037). This makes the score gate-worthy without changing the model:

Tests: risk-tier1-test.sh (+15: score_tier1, _score_size, has_source_files, risk_floor, both e2e fixtures, empty-list fail-closed), post-review-test.sh (+11: floor raises / never lowers, degraded header, history row, garbage provenance dropped, legacy results unchanged). make check-bundle, shellcheck, make lint green (two context-budget ceilings bumped for the added prose).

Refs #1037, #1227, fullsend-ai/fullsend#4698.

@github-actions

Copy link
Copy Markdown

Functional tests did not run

Functional tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Harden risk scoring with deterministic Tier 1 and security floors

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Computes deterministic Tier 1 scores and security-sensitive floors in the collection script.
• Falls back to Tier 1 metadata when risk sub-agent scoring fails.
• Fails closed on missing signals and preserves validated score history in sticky comments.
Diagram

graph TD
  A["PR Signals"] --> B["Tier 1 Script"] --> C{"Sub-agent succeeds?"}
  C -->|Yes| D["Composite Score"] --> E["Floor Enforcement"] --> F["Risk Label"] --> G["Risk History Comment"]
  C -->|No| H["Tier 1 Fallback"] --> E
  G -.->|Prior rows| E
Loading
High-Level Assessment

The selected approach is appropriate: deterministic inputs are scored once in the shell script, while the final consumer independently validates provenance and enforces the security floor. Re-deriving Tier 1 in the LLM would preserve score drift, and dropping scores on sub-agent failure would retain the existing observability gap. Carrying bounded, shape-validated history in the sticky GitHub comment is also proportionate without introducing external persistence.

Files changed (10) +439 / -52

Enhancement (3) +216 / -32
post-review.shEnforce floors and publish risk history +47/-1

Enforce floors and publish risk history

• Updates the generated post-review bundle to validate Tier 1 provenance, enforce risk floors, render degraded metadata, and preserve twenty validated GitHub history rows.

scripts/post-review.sh

post-review.src.shAdd post-review risk hardening source logic +47/-1

Add post-review risk hardening source logic

• Validates script-originated risk fields, raises scores below the security floor, and enriches sticky comments with Tier 1 provenance and bounded per-head history.

scripts/post-review.src.sh

risk-tier1.shCompute Tier 1 scores and security floors +122/-30

Compute Tier 1 scores and security floors

• Implements the documented Tier 1 scoring table, source-file detection, and security-path floor. It also emits all signals as unknown for missing or empty file lists and appends authoritative score and floor fields.

skills/pr-risk-assessment/scripts/risk-tier1.sh

Tests (2) +163 / -3
post-review-test.shTest risk floors, provenance, degradation, and history +99/-1

Test risk floors, provenance, degradation, and history

• Captures posted markdown comments and adds regression coverage for floor enforcement, degraded metadata, history rows, malformed provenance, and legacy result rendering.

scripts/post-review-test.sh

risk-tier1-test.shExpand deterministic Tier 1 regression coverage +64/-2

Expand deterministic Tier 1 regression coverage

• Adds scoring-table, change-size, source-file, security-floor, end-to-end output, pagination, and empty-file-list fail-closed tests.

scripts/risk-tier1-test.sh

Documentation (1) +9 / -0
review.mdDocument hardened risk assessment behavior +9/-0

Document hardened risk assessment behavior

• Explains deterministic Tier 1 scoring, security-sensitive floors, degraded results, and sticky-comment history. Warns routing and gating consumers to treat degraded assessments as unavailable scores.

docs/review.md

Other (4) +51 / -17
.skillsaw-baseline.jsonRaise context-budget baseline ceilings +4/-4

Raise context-budget baseline ceilings

• Refreshes recorded token estimates for the expanded PR review and risk-assessment skill instructions.

.skillsaw-baseline.json

SKILL.mdDefine Tier 1-only orchestration fallback +17/-5

Define Tier 1-only orchestration fallback

• Instructs the review orchestrator to run the Tier 1 script when the risk sub-agent fails and emit a marked degraded assessment when deterministic scoring remains available.

skills/pr-review/SKILL.md

risk-assessment.mdRequire script-computed Tier 1 provenance +9/-3

Require script-computed Tier 1 provenance

• Directs the risk sub-agent to copy the script's Tier 1 score and floor, use them in composite scoring, and include both fields in valid output.

skills/pr-review/sub-agents/risk-assessment.md

SKILL.mdMake deterministic scoring and floors authoritative +21/-5

Make deterministic scoring and floors authoritative

• Documents the script-computed Tier 1 composite and security floor, extends the output schema, and prohibits normal LLM re-derivation of deterministic signals.

skills/pr-risk-assessment/SKILL.md

@qodo-code-review

qodo-code-review Bot commented Sep 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 🔗 Cross-repo conflicts (1) 📜 Skill insights (4)

Grey Divider


Action required

1. Risk-enabled reviews never post ✓ Resolved 🔗 Cross-repo conflict ≡ Correctness
Description
review-result.schema.json closes risk_assessment with additionalProperties: false but does not
declare the newly required tier1_score, risk_floor, or fallback degraded fields. Normal and
Tier-1-only fallback assessments containing these fields fail Fullsend validation before
post-review.sh can apply the score, floor, labels, metadata, or history and publish the review
comment and risk artifacts.
Code

skills/pr-review/SKILL.md[R631-634]

+absence is not an error. Otherwise emit `risk_assessment` from the
+script alone: `score = max(round(TIER1_SCORE), RISK_FLOOR)`, `level`
+per the mapping, `tier1_score`, `risk_floor`, `degraded: "tier1-only"`,
+and the rationale "Risk sub-agent unavailable; tier-1 metadata only."
Relevance

●●● Strong

Schema and producer contract mismatches are accepted when newly emitted fields can prevent valid
results from posting.

PR-#861
PR-#1085

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The updated risk producer contract requires tier1_score and risk_floor for normal results and
degraded for the fallback, while the closed review-result schema permits only the existing signal
arrays and rationale. Fullsend validates agent output against that schema before invoking the
configured post-script and explicitly skips the script when validation is unsuccessful,
demonstrating that the producer and validator contracts no longer agree and that neither result form
reaches posting.

skills/pr-review/SKILL.md[620-637]
schemas/review-result.schema.json[30-54]
harness/review.yaml[43-52]
scripts/validate-output-schema.sh[49-69]
skills/pr-review/SKILL.md[629-637]
skills/pr-review/sub-agents/risk-assessment.md[59-74]
harness/review.yaml[43-48]
skills/pr-review/SKILL.md[629-634]
skills/pr-review/sub-agents/risk-assessment.md[71-73]
skills/pr-review/sub-agents/risk-assessment.md[59-73]
scripts/validate-output-schema.sh[49-68]
External repo: fullsend-ai/fullsend, internal/cli/run.go [1704-1723]
Skill: pr-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The risk-assessment instructions require `tier1_score`, `risk_floor`, and the optional fallback field `degraded`, but `risk_assessment` has `additionalProperties: false` and declares none of them. Fullsend therefore rejects normal provenance results and Tier-1-only degraded fallbacks before the post-review script can consume them.

## Fix Focus Areas
- schemas/review-result.schema.json[30-54]
- skills/pr-review/sub-agents/risk-assessment.md[59-73]
- skills/pr-review/SKILL.md[629-634]

## Recommended Fix
Extend `risk_assessment.properties` with a numeric `tier1_score` constrained to 1–5, an integer `risk_floor` constrained to 1–5, and a `degraded` string restricted to the supported `tier1-only` value, using constraints consistent with the skill and posting script. Keep these fields optional so legacy and UNKNOWN/no-score results remain valid, then add schema-validation coverage for normal provenance metadata, degraded fallback, legacy, and all-UNKNOWN results.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Older comments pollute risk history 📜 Skill insight ≡ Correctness
Description
The new gh api --paginate --jq pipeline selects last separately on every response page and feeds
every selected comment body into the row collector. Once a pull request has comments on multiple
pages, rows from older page-level comments join those from the actual latest sticky comment and
consume the 20-row history.
Code

scripts/post-review.src.sh[R484-486]

+      PRIOR_ROWS=$(GH_TOKEN="${REVIEW_TOKEN}" gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" \
+        --jq '[.[] | select(.body | contains("<!-- fullsend:risk-assessment -->"))] | last | .body // empty' 2>/dev/null \
+        | grep -E "${ROW_RE}" || true)
Relevance

●●● Strong

Recent pagination-related correctness findings were accepted when page aggregation could omit or
distort repository data.

PR-#708
PR-#861

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The GitHub API producer can emit multiple page-level results, but the downstream grep and row
accumulator consume them as though they came from one prior comment. The new history contract
therefore has incompatible pagination output and consumer expectations.

scripts/post-review.src.sh[483-493]
scripts/post-review.sh[893-903]
Skill: pr-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Pagination currently produces one selected sticky comment per API page, while the history collector expects one globally latest prior comment.

## Fix Focus Areas
- scripts/post-review.src.sh[484-493]
- scripts/post-review.sh[894-903]
- scripts/post-review-test.sh[1852-1859]

## Recommended Fix
Slurp all paginated comment responses before selecting the globally latest matching sticky comment, then extract rows only from that body. Regenerate the bundled script and add a multi-page fixture containing older sticky comments.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Security changes can receive low risk 📜 Skill insight ≡ Correctness
Description
post-review.sh treats the optional model-emitted risk_floor as authoritative after checking only
that it is a digit from 1 to 5, without verifying it against changed paths or trusted Tier 1 output.
If the field is omitted or set to 1 for a security-sensitive change, the floor branch is skipped
and the unchanged score proceeds to risk labeling and the sticky comment.
Code

scripts/post-review.src.sh[R436-437]

+  RISK_FLOOR=$(jq -r '.risk_assessment.risk_floor // empty' "${RESULT_FILE}")
+  [[ "${RISK_FLOOR}" =~ ^[1-5]$ ]] || RISK_FLOOR=""
Relevance

●●● Strong

Accepted security hardening precedents favor validating trusted risk metadata and preventing low
security scores.

PR-#415
PR-#861

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Tier 1 script authoritatively computes a floor of 2 when security-sensitive paths are detected,
but the posting script neither invokes that computation nor consumes trusted output from it.
Instead, it applies a floor only when agent-result.json already contains a syntactically valid
higher value, without establishing that 1 corresponds to an absence of security-sensitive changes;
the same script then derives and applies the label and sticky comment from the unchanged score and
level, proving that an omitted or valid-looking incorrect model value breaks the intended
producer-to-consumer security invariant.

scripts/post-review.src.sh[433-449]
skills/pr-review/sub-agents/risk-assessment.md[25-31]
skills/pr-risk-assessment/scripts/risk-tier1.sh[207-212]
skills/pr-risk-assessment/scripts/risk-tier1.sh[287-289]
skills/pr-risk-assessment/SKILL.md[76-81]
scripts/post-review.src.sh[451-465]
Skill: pr-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The security floor is accepted from optional, model-emitted JSON rather than independently derived from trusted path analysis, so a missing, malformed, conflicting, or incorrectly low value can bypass the security-path guard.

## Fix Focus Areas
- scripts/post-review.src.sh[433-449]
- scripts/post-review.sh[843-859]
- scripts/post-review-test.sh[1829-1844]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[207-212]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[229-289]
- skills/pr-review/sub-agents/risk-assessment.md[26-31]

## Recommended Fix
Obtain the authoritative floor outside the sub-agent by consuming trusted Tier 1 output, rerunning `risk-tier1.sh` in the posting path, or independently inspecting trusted changed-path metadata. Compare any submitted model field with the trusted value and enforce the trusted floor; if the trusted measurement is missing, malformed, or conflicting, fail closed by withholding a low-risk label rather than allowing the model value to reduce protection. Regenerate `scripts/post-review.sh` from its source and add tests covering omitted and incorrectly low model-provided floors.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Users can forge the risk history 🐞 Bug ⛨ Security
Description
PRIOR_ROWS selects the latest comment containing the public risk marker and copies every line
matching ROW_RE into the replacement sticky comment without verifying the author, comment
identifier, or other trusted provenance. When a PR participant posts a newer marker-bearing comment
with conforming fabricated rows, the next review carries its scores and head hashes into the
displayed history as apparent bot-generated provenance.
Code

scripts/post-review.src.sh[R483-486]

+      ROW_RE='^\| `[0-9a-f]{6,7}` \| [0-9]{4}-[0-9]{2}-[0-9]{2} \| [1-5]/5 [a-z]+ \| [0-9.-]+ \| [a-z0-9-]* \|$'
+      PRIOR_ROWS=$(GH_TOKEN="${REVIEW_TOKEN}" gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" \
+        --jq '[.[] | select(.body | contains("<!-- fullsend:risk-assessment -->"))] | last | .body // empty' 2>/dev/null \
+        | grep -E "${ROW_RE}" || true)
Relevance

●●● Strong

Recent security precedents accept preventing untrusted user-controlled data from influencing posted
workflow artifacts.

PR-#508
PR-#592

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The comment query filters only on marker presence, even though that marker is visible in every
posted risk comment and supplied directly to the posting command; it then copies every
shape-matching row from the selected body into RISK_HISTORY and renders it unchanged. No author,
comment identifier, or other trusted provenance check appears before extraction and rendering, so
valid-looking user-controlled rows are accepted.

scripts/post-review.src.sh[475-493]
scripts/post-review.src.sh[505-510]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Risk history is imported from any marker-bearing PR comment, allowing another commenter to inject fabricated scores, head hashes, and provenance into the bot's next sticky comment.

## Fix Focus Areas
- scripts/post-review.src.sh[475-493]
- scripts/post-review.src.sh[505-510]
- scripts/post-review-test.sh[1852-1859]

## Recommended Fix
Resolve the authenticated Fullsend review actor and restrict the comment query to marker-bearing comments authored by that identity before selecting a comment and extracting rows. Alternatively, fetch the exact sticky comment identifier maintained by `fullsend post-comment`. Preserve strict row-shape validation as defense in depth, add a test fixture proving that a newer user-authored marker comment with conforming rows is ignored, and regenerate `scripts/post-review.sh`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Config-only changes get inflated risk 📜 Skill insight ≡ Correctness
Description
has_source_files treats every basename outside a short extension list as source, so config files
such as .env and .properties are not recognized as config-only. A config-only pull request with
no tests then takes the source-path 0.00 score of 5 instead of the documented neutral score of 1,
inflating the Tier 1 composite.
Code

skills/pr-risk-assessment/scripts/risk-tier1.sh[R141-143]

+    case "${file##*/}" in
+      *.md|*.markdown|*.rst|*.adoc|*.txt|*.yaml|*.yml|*.json|*.toml|*.ini|*.cfg|*.conf|LICENSE*|CODEOWNERS|.gitignore|.gitattributes|.editorconfig) ;;
+      *) echo "true"; return ;;
Relevance

●●● Strong

Recent risk-scoring precedents accepted classifier edge cases that miscomputed source, security, or
dependency dimensions.

PR-#861

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The scoring documentation says config-only changes receive a neutral score, while the new classifier
recognizes only the enumerated basenames and extensions. Unlisted configuration formats follow the
source branch and receive the documented worst score for a zero test ratio.

skills/pr-risk-assessment/SKILL.md[56-68]
skills/pr-risk-assessment/SKILL.md[76-81]
skills/pr-risk-assessment/scripts/risk-tier1.sh[136-147]
skills/pr-risk-assessment/scripts/risk-tier1.sh[191-196]
Skill: pr-review

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new source-file classifier does not cover common configuration filenames and extensions, contradicting the documented neutral treatment of config-only changes.

## Fix Focus Areas
- skills/pr-risk-assessment/scripts/risk-tier1.sh[136-147]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[191-196]
- scripts/risk-tier1-test.sh[332-338]

## Recommended Fix
Expand the non-source classification using the repository's established configuration-file patterns, including extensionless and dotfile configurations where appropriate. Add tests showing representative config-only changes receive the neutral test-ratio score while mixed source changes do not.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Temporary API failures erase risk history 🐞 Bug ☼ Reliability
Description
The prior-comment fetch suppresses all gh and parsing errors and converts them to an empty
PRIOR_ROWS value. On a transient GitHub failure, the script still replaces the sticky comment
using only the current row, so all previously retained history disappears.
Code

scripts/post-review.src.sh[R484-491]

+      PRIOR_ROWS=$(GH_TOKEN="${REVIEW_TOKEN}" gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" \
+        --jq '[.[] | select(.body | contains("<!-- fullsend:risk-assessment -->"))] | last | .body // empty' 2>/dev/null \
+        | grep -E "${ROW_RE}" || true)
+      NEW_ROW=""
+      if [[ -n "${RISK_HEAD}" && "${RISK_SCORE}" =~ ^[1-5]$ ]]; then
+        NEW_ROW="| \`${RISK_HEAD:0:7}\` | $(date -u +%Y-%m-%d) | ${RISK_SCORE}/5 ${RISK_LEVEL} | ${TIER1_SCORE:--} | ${RISK_DEGRADED} |"
+      fi
+      ROWS=$(printf '%s\n%s\n' "${PRIOR_ROWS}" "${NEW_ROW}" | sed '/^$/d' | tail -n 20)
Relevance

●● Moderate

History loss on transient failures is plausible, but precedent is mixed on preserving best-effort
behavior versus surfacing errors.

PR-#1124
PR-#1068

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Errors are redirected away and neutralized with || true, after which history is assembled only
from the empty value and the comment-post command still runs.

scripts/post-review.src.sh[479-510]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

Issue description
A failed GitHub history fetch is treated identically to a PR with no history, so the next marker-based comment update overwrites the existing history table.

Fix Focus Areas
- scripts/post-review.src.sh[479-510]

Recommended Fix
Capture and check the API and query exit status separately from the no-matching-comment case. If fetching prior comments fails, skip the risk-comment replacement and emit a warning, or otherwise retain the existing sticky body rather than publishing a replacement with empty history; add a regression test for a failing comments API call.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
7. Risk history duplicates old rows 🔗 Cross-repo conflict ≡ Correctness
Description
post-review.src.sh copies matching rows from the existing comment into a new 20-row table, but it
posts that table without disabling Fullsend's separate sticky-comment history. On each re-review
Fullsend archives the previous table as another Previous run block, and the next grep reimports
rows from both active and archived tables, creating duplicates that can displace unique head
revisions.
Code

scripts/post-review.src.sh[R491-493]

+      ROWS=$(printf '%s\n%s\n' "${PRIOR_ROWS}" "${NEW_ROW}" | sed '/^$/d' | tail -n 20)
+      if [[ -n "${ROWS}" ]]; then
+        RISK_HISTORY=$'\n\n<details>\n<summary>History</summary>\n\n| head | date | score | tier 1 | note |\n|---|---|---|---|---|\n'"${ROWS}"$'\n\n</details>'
Relevance

●● Moderate

The duplication depends on external sticky-comment behavior and lacks a close repository precedent
for this exact history design.

PR-#708

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR gathers every matching row from the prior sticky comment and constructs its own history,
while Fullsend defaults keep_history to true and wraps previous comment content into retained
history blocks. Because the next fetch greps the whole body, rows inside those retained blocks are
admitted again.

scripts/post-review.src.sh[475-493]
scripts/post-review.src.sh[497-510]
External repo: fullsend-ai/fullsend, internal/cli/issues.go [495-515]
External repo: fullsend-ai/fullsend, internal/cli/postcomment.go [72-103]
External repo: fullsend-ai/fullsend, internal/sticky/sticky.go [120-147]
External repo: fullsend-ai/fullsend, internal/sticky/sticky.go [163-220]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The risk script now maintains its own bounded history, while `fullsend post-comment` independently preserves every prior comment by default. Archived rows are fetched again on later runs, causing duplicates in the active history table.

## Fix Focus Areas
- scripts/post-review.src.sh[475-510]
- scripts/post-review.sh[885-916]

## Recommended Fix
Pass `--keep-history=false` when posting the risk comment so only the script's bounded per-head history is retained. Regenerate the bundled `scripts/post-review.sh` from the source script and extend the test mock to verify that history preservation is disabled.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

8. One dependency file looks like several 🐞 Bug ≡ Correctness
Description
score_tier1 treats the presence of a comma in DEPENDENCY_FILES_CHANGED as proof that two or more
dependency files changed. A single matching manifest whose filename contains a comma is emitted as
one comma-joined value by find_dependency_files, then receives the table's five-point multi-file
score instead of the three-point one-file score.
Code

skills/pr-risk-assessment/scripts/risk-tier1.sh[190]

+  case "${depfiles}" in UNKNOWN) ;; none) _add 1 ;; *,*) _add 5 ;; *) _add 3 ;; esac
Relevance

●●● Strong

Recent risk-scoring precedents accepted deterministic dependency classification bugs and added
edge-case coverage.

PR-#861

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The helper joins file paths using commas, while the new scorer distinguishes one from multiple files
exclusively by whether the resulting string contains a comma; the scoring table assigns different
values to those cases.

skills/pr-risk-assessment/scripts/risk-tier1.sh[92-107]
skills/pr-risk-assessment/scripts/risk-tier1.sh[179-203]
skills/pr-risk-assessment/SKILL.md[62-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

Issue description
The dependency score infers the number of changed manifests from a comma-delimited display string, so a comma in one filename is misclassified as multiple manifests.

Fix Focus Areas
- skills/pr-risk-assessment/scripts/risk-tier1.sh[92-107]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[179-203]

Recommended Fix
Pass the dependency-file count to `score_tier1` separately from the human-readable dependency list, and score that count directly. Retain the joined list only for emitted diagnostics and add a regression test for a single matching filename containing a comma.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Risk changes require human approval 📜 Skill insight § Compliance
Description
This PR changes executable files under scripts/ and agent instructions under skills/, both
protected paths named by the checklist. The linked issues explain the work, but these
governance-sensitive changes still require human approval rather than automated approval.
Code

skills/pr-risk-assessment/scripts/risk-tier1.sh[R138-140]

+has_source_files() {
+  local file
+  for file in "$@"; do
Relevance

● Weak

Recent governance-related requests to enforce protected-path approval rules were explicitly
rejected.

PR-#1196
PR-#753

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist mandates a finding whenever protected governance or infrastructure paths are modified
and states that such a PR must never be auto-approved. The diff changes multiple files beneath both
protected path prefixes, while the PR description supplies issue references that justify the scope
but do not remove the human-review requirement.

skills/pr-risk-assessment/scripts/risk-tier1.sh[136-153]
scripts/post-review.src.sh[433-450]
skills/pr-review/SKILL.md[620-637]
Skill: pr-review


Grey Divider

Context sources
✅ Compliance rules (platform): 57 rules
✅ Skills: 4 invoked
  code-review
  code-implementation
  pr-review
  docs-review
✅ Cross-repo context — repo relationships
  Explored: repo: fullsend-ai/autonomy-analysis (sha: 46717359)
  Explored: repo: fullsend-ai/fullsend (sha: a4b5c485)
Review mode: 🧠 Deep: This is a behavior-changing, security-sensitive shell workflow spanning multiple scripts, scoring/fallback logic, validation, GitHub history handling, and orchestration paths, with many independent sites where subtle defects could be missed in one pass.

Grey Divider

Tip of the day
💡 Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +436 to +437
RISK_FLOOR=$(jq -r '.risk_assessment.risk_floor // empty' "${RESULT_FILE}")
[[ "${RISK_FLOOR}" =~ ^[1-5]$ ]] || RISK_FLOOR=""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Security changes can receive low risk 📜 Skill insight ≡ Correctness

post-review.sh treats the optional model-emitted risk_floor as authoritative after checking only
that it is a digit from 1 to 5, without verifying it against changed paths or trusted Tier 1 output.
If the field is omitted or set to 1 for a security-sensitive change, the floor branch is skipped
and the unchanged score proceeds to risk labeling and the sticky comment.
Agent Prompt
## Issue description
The security floor is accepted from optional, model-emitted JSON rather than independently derived from trusted path analysis, so a missing, malformed, conflicting, or incorrectly low value can bypass the security-path guard.

## Fix Focus Areas
- scripts/post-review.src.sh[433-449]
- scripts/post-review.sh[843-859]
- scripts/post-review-test.sh[1829-1844]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[207-212]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[229-289]
- skills/pr-review/sub-agents/risk-assessment.md[26-31]

## Recommended Fix
Obtain the authoritative floor outside the sub-agent by consuming trusted Tier 1 output, rerunning `risk-tier1.sh` in the posting path, or independently inspecting trusted changed-path metadata. Compare any submitted model field with the trusted value and enforce the trusted floor; if the trusted measurement is missing, malformed, or conflicting, fail closed by withholding a low-risk label rather than allowing the model value to reduce protection. Regenerate `scripts/post-review.sh` from its source and add tests covering omitted and incorrectly low model-provided floors.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +484 to +486
PRIOR_ROWS=$(GH_TOKEN="${REVIEW_TOKEN}" gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" \
--jq '[.[] | select(.body | contains("<!-- fullsend:risk-assessment -->"))] | last | .body // empty' 2>/dev/null \
| grep -E "${ROW_RE}" || true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Older comments pollute risk history 📜 Skill insight ≡ Correctness

The new gh api --paginate --jq pipeline selects last separately on every response page and feeds
every selected comment body into the row collector. Once a pull request has comments on multiple
pages, rows from older page-level comments join those from the actual latest sticky comment and
consume the 20-row history.
Agent Prompt
## Issue description
Pagination currently produces one selected sticky comment per API page, while the history collector expects one globally latest prior comment.

## Fix Focus Areas
- scripts/post-review.src.sh[484-493]
- scripts/post-review.sh[894-903]
- scripts/post-review-test.sh[1852-1859]

## Recommended Fix
Slurp all paginated comment responses before selecting the globally latest matching sticky comment, then extract rows only from that body. Regenerate the bundled script and add a multi-page fixture containing older sticky comments.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +141 to +143
case "${file##*/}" in
*.md|*.markdown|*.rst|*.adoc|*.txt|*.yaml|*.yml|*.json|*.toml|*.ini|*.cfg|*.conf|LICENSE*|CODEOWNERS|.gitignore|.gitattributes|.editorconfig) ;;
*) echo "true"; return ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Config-only changes get inflated risk 📜 Skill insight ≡ Correctness

has_source_files treats every basename outside a short extension list as source, so config files
such as .env and .properties are not recognized as config-only. A config-only pull request with
no tests then takes the source-path 0.00 score of 5 instead of the documented neutral score of 1,
inflating the Tier 1 composite.
Agent Prompt
## Issue description
The new source-file classifier does not cover common configuration filenames and extensions, contradicting the documented neutral treatment of config-only changes.

## Fix Focus Areas
- skills/pr-risk-assessment/scripts/risk-tier1.sh[136-147]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[191-196]
- scripts/risk-tier1-test.sh[332-338]

## Recommended Fix
Expand the non-source classification using the repository's established configuration-file patterns, including extensionless and dotfile configurations where appropriate. Add tests showing representative config-only changes receive the neutral test-ratio score while mixed source changes do not.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +483 to +486
ROW_RE='^\| `[0-9a-f]{6,7}` \| [0-9]{4}-[0-9]{2}-[0-9]{2} \| [1-5]/5 [a-z]+ \| [0-9.-]+ \| [a-z0-9-]* \|$'
PRIOR_ROWS=$(GH_TOKEN="${REVIEW_TOKEN}" gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" \
--jq '[.[] | select(.body | contains("<!-- fullsend:risk-assessment -->"))] | last | .body // empty' 2>/dev/null \
| grep -E "${ROW_RE}" || true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Users can forge the risk history 🐞 Bug ⛨ Security

PRIOR_ROWS selects the latest comment containing the public risk marker and copies every line
matching ROW_RE into the replacement sticky comment without verifying the author, comment
identifier, or other trusted provenance. When a PR participant posts a newer marker-bearing comment
with conforming fabricated rows, the next review carries its scores and head hashes into the
displayed history as apparent bot-generated provenance.
Agent Prompt
## Issue description
Risk history is imported from any marker-bearing PR comment, allowing another commenter to inject fabricated scores, head hashes, and provenance into the bot's next sticky comment.

## Fix Focus Areas
- scripts/post-review.src.sh[475-493]
- scripts/post-review.src.sh[505-510]
- scripts/post-review-test.sh[1852-1859]

## Recommended Fix
Resolve the authenticated Fullsend review actor and restrict the comment query to marker-bearing comments authored by that identity before selecting a comment and extracting rows. Alternatively, fetch the exact sticky comment identifier maintained by `fullsend post-comment`. Preserve strict row-shape validation as defense in depth, add a test fixture proving that a newer user-authored marker comment with conforming rows is ignored, and regenerate `scripts/post-review.sh`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +484 to +491
PRIOR_ROWS=$(GH_TOKEN="${REVIEW_TOKEN}" gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" \
--jq '[.[] | select(.body | contains("<!-- fullsend:risk-assessment -->"))] | last | .body // empty' 2>/dev/null \
| grep -E "${ROW_RE}" || true)
NEW_ROW=""
if [[ -n "${RISK_HEAD}" && "${RISK_SCORE}" =~ ^[1-5]$ ]]; then
NEW_ROW="| \`${RISK_HEAD:0:7}\` | $(date -u +%Y-%m-%d) | ${RISK_SCORE}/5 ${RISK_LEVEL} | ${TIER1_SCORE:--} | ${RISK_DEGRADED} |"
fi
ROWS=$(printf '%s\n%s\n' "${PRIOR_ROWS}" "${NEW_ROW}" | sed '/^$/d' | tail -n 20)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

6. Temporary api failures erase risk history 🐞 Bug ☼ Reliability

The prior-comment fetch suppresses all gh and parsing errors and converts them to an empty
PRIOR_ROWS value. On a transient GitHub failure, the script still replaces the sticky comment
using only the current row, so all previously retained history disappears.
Agent Prompt
Issue description
A failed GitHub history fetch is treated identically to a PR with no history, so the next marker-based comment update overwrites the existing history table.

Fix Focus Areas
- scripts/post-review.src.sh[479-510]

Recommended Fix
Capture and check the API and query exit status separately from the no-matching-comment case. If fetching prior comments fails, skip the risk-comment replacement and emit a warning, or otherwise retain the existing sticky body rather than publishing a replacement with empty history; add a regression test for a failing comments API call.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

case "${prot}" in UNKNOWN) ;; 0) _add 1 ;; 1) _add 3 ;; *) _add 5 ;; esac
case "${sec}" in UNKNOWN) ;; 0) _add 1 ;; 1) _add 3 ;; 2|3) _add 4 ;; *) _add 5 ;; esac
case "${ci}" in true) _add 4 ;; false) _add 1 ;; esac
case "${depfiles}" in UNKNOWN) ;; none) _add 1 ;; *,*) _add 5 ;; *) _add 3 ;; esac

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

8. One dependency file looks like several 🐞 Bug ≡ Correctness

score_tier1 treats the presence of a comma in DEPENDENCY_FILES_CHANGED as proof that two or more
dependency files changed. A single matching manifest whose filename contains a comma is emitted as
one comma-joined value by find_dependency_files, then receives the table's five-point multi-file
score instead of the three-point one-file score.
Agent Prompt
Issue description
The dependency score infers the number of changed manifests from a comma-delimited display string, so a comma in one filename is misclassified as multiple manifests.

Fix Focus Areas
- skills/pr-risk-assessment/scripts/risk-tier1.sh[92-107]
- skills/pr-risk-assessment/scripts/risk-tier1.sh[179-203]

Recommended Fix
Pass the dependency-file count to `score_tier1` separately from the human-readable dependency list, and score that count directly. Retain the joined list only for emitted diagnostics and add a regression test for a single matching filename containing a comma.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread skills/pr-review/SKILL.md
Comment on lines +491 to +493
ROWS=$(printf '%s\n%s\n' "${PRIOR_ROWS}" "${NEW_ROW}" | sed '/^$/d' | tail -n 20)
if [[ -n "${ROWS}" ]]; then
RISK_HISTORY=$'\n\n<details>\n<summary>History</summary>\n\n| head | date | score | tier 1 | note |\n|---|---|---|---|---|\n'"${ROWS}"$'\n\n</details>'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

7. Risk history duplicates old rows 🔗 Cross-repo conflict ≡ Correctness

post-review.src.sh copies matching rows from the existing comment into a new 20-row table, but it
posts that table without disabling Fullsend's separate sticky-comment history. On each re-review
Fullsend archives the previous table as another Previous run block, and the next grep reimports
rows from both active and archived tables, creating duplicates that can displace unique head
revisions.
Agent Prompt
## Issue description
The risk script now maintains its own bounded history, while `fullsend post-comment` independently preserves every prior comment by default. Archived rows are fetched again on later runs, causing duplicates in the active history table.

## Fix Focus Areas
- scripts/post-review.src.sh[475-510]
- scripts/post-review.sh[885-916]

## Recommended Fix
Pass `--keep-history=false` when posting the risk comment so only the script's bounded per-head history is retained. Regenerate the bundled `scripts/post-review.sh` from the source script and extend the test mock to verify that history preservation is disabled.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

…grade instead of drop, keep history

ADR 0089 leaves tier 1 to a bash script but has the sub-agent re-emit it,
which is how the same PR scored 1, 2, 1 across three re-reviews (fullsend-ai#1037).
risk-tier1.sh now ends with TIER1_SCORE and RISK_FLOOR — the SKILL.md
table computed once, deterministically — and the sub-agent copies them
into tier1_score / risk_floor instead of re-deriving.

RISK_FLOOR is 2 whenever a security-sensitive path is touched.
post-review.sh enforces it from the echoed field, so risk/low cannot be
applied to a security PR whatever the LLM returned. Over the 246
production PRs measured on fullsend#4698, no score-1 PR touched such a
path, so the floor changes nothing today and closes the gap for later.

When the sub-agent fails, the orchestrator no longer drops the score: it
runs the script itself and emits max(round(TIER1_SCORE), RISK_FLOOR) with
degraded: "tier1-only". Four production reviews since 08-25 lost their
score to "claude-sonnet-4-5@20250929 is not available" (the error behind
fullsend#6922, visible from 08-25); consumers must treat degraded as no
score.

The sticky risk comment now carries the tier 1 value, the degraded
marker, and a per-head-SHA history table carried forward from the prior
comment (GitHub only, 20 rows, rows re-admitted only when they match the
exact shape this script writes), so drift across re-reviews is visible
on the PR instead of only in run artifacts.

Tests: risk-tier1-test.sh covers score_tier1, _score_size,
has_source_files, risk_floor and both e2e fixtures; post-review-test.sh
covers floor raises/never lowers, degraded header, history row,
garbage provenance dropped, and legacy results unchanged. Baseline
ceilings for the two SKILL.md files bumped for the added prose.

Refs fullsend-ai#1037, fullsend-ai/fullsend#4698

Signed-off-by: guy oron <goron@redhat.com>

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review findings (review-only, no verdict change)

Verified at head e6026cd. Three findings are posted inline below; this one has no line in the diff to anchor to, because the file the fix belongs in is not part of this PR.


CRITICAL — review-result schema rejects the new risk fields; the fix must land in schemas/review-result.schema.json, which this PR never touches

schemas/review-result.schema.json:54 declares risk_assessment with additionalProperties: false, permitting only score, level, rationale, tier1_signals, tier2_signals, tier3_signals. That file is not in this PR's diff at all (gh pr diff 1245 lists 10 files; this is not one of them).

Meanwhile the PR makes the new fields mandatory:

  • skills/pr-review/sub-agents/risk-assessment.md:71-73 (added): "rationale, tier1_score and risk_floor are required"
  • skills/pr-risk-assessment/SKILL.md:191-193 (added): lists them under Required fields
  • skills/pr-review/SKILL.md:633 (added): tells the orchestrator fallback to emit degraded: "tier1-only" as well

harness/review.yaml wires scripts/validate-output-schema.sh as a validation_loop with max_iterations: 1. So a compliant result fails validation and the whole review — verdict, findings, labels, risk comment — is dropped. A result that instead obeys agents/review.md's "only include fields listed below — the schema is strict" strips the fields, and the floor / provenance / degraded features are silently inert.

FEATURES.md:123 ("Add the field to schemas/<agent>-result.schema.json") was skipped, and agents/review.md's field table was not updated.

Suggestion: add to properties.risk_assessment in schemas/review-result.schema.json:

  • tier1_score — number, minimum: 1, maximum: 5
  • risk_floor — integer, minimum: 1, maximum: 5
  • degraded — string, enum: ["tier1-only"]

All three optional, so legacy and UNKNOWN results stay valid. Mirror them in agents/review.md's documented field table, and extend scripts/validate-output-schema-test.sh (which already loads REVIEW_SCHEMA at line 242) with the exact RISK_FLOORED_RESULT / RISK_DEGRADED_RESULT / RISK_BAD_PROVENANCE fixtures from post-review-test.sh, so the two suites cannot drift again.

Note: qodo-code-review flagged this same class from the prompt side at skills/pr-review/SKILL.md:634 (comment 3979232169). This entry anchors it on the file where the fix actually lands, where no comment exists.

TIER1_SCORE=$(jq -r '.risk_assessment.tier1_score // empty' "${RESULT_FILE}")
[[ "${TIER1_SCORE}" =~ ^[1-5](\.[0-9]{1,2})?$ ]] || TIER1_SCORE=""
RISK_DEGRADED=$(jq -r '.risk_assessment.degraded // empty' "${RESULT_FILE}")
[[ "${RISK_DEGRADED}" =~ ^[a-z0-9-]{1,32}$ ]] || RISK_DEGRADED=""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — degraded is parsed and validated but never consulted when the risk label is applied

Verified at head e6026cd. RISK_DEGRADED is read and validated here at lines 440-441 (added by this PR), then referenced only twice more: line 473 appends it to the comment header via RISK_META, and line 489 puts it in the history row.

The labelling block at 451-465 keys purely on RISK_LEVELforge_create_label "risk/${RISK_LEVEL}" / forge_add_label_edit "risk/${RISK_LEVEL}" — with no RISK_DEGRADED check.

This PR simultaneously adds the contract "Anything that routes or gates on the score must treat degraded as 'no score'" in two added places: docs/review.md:76-77 and skills/pr-review/SKILL.md:636-637. The label is the only surface automation keys on, and a tier-1-only fallback score produces a risk/high (or any other) label byte-identical to a fully-computed one — so no consumer can honour the contract the PR just documented.

To be precise about scope: there is a visible marker — the sticky comment header carries · degraded: tier1-only. The gap is specifically the label.

Suggestion: either skip forge_create_label / forge_add_label_edit when RISK_DEGRADED is non-empty (matching the "no score" contract literally), or apply an additional risk/degraded marker label alongside the level so label consumers can tell the two apart. Add a post-review-test.sh case asserting the chosen behaviour for RISK_DEGRADED_RESULT.


**`TIER1_SCORE` and `RISK_FLOOR`:** the script's last two lines are the
Tier 1 composite computed from this table and the floor — `2` when any
security-sensitive path is touched, else `1`. Use both as given; the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — this file now carries both "assign a sub-score and average" and "do not re-derive it" for Tier 1 — the exact intro/procedure split that caused the bug this PR fixes

Verified at head e6026cd. The PR adds this paragraph at lines 76-81 — "Use both as given; the script computes them so the score is the same on every run (re-deriving deterministic signals in the LLM is what made the same PR flip between 1 and 2 across re-reviews)" — and rewrites procedure step 2 at 214-217 to "Take the Tier 1 composite from the script: TIER1_SCORE is the Tier 1 composite. Do not re-derive it."

But the section intro at lines 51-54 is unchanged and still reads as an imperative:

The risk-tier1.sh script outputs these KEY=VALUE signals. Evaluate each dimension and assign a 1-5 sub-score. Then average the dimension sub-scores for the Tier 1 composite.

An LLM reading the section header first gets exactly the instruction this PR is trying to eliminate.

Separately confirmed: skills/pr-review/SKILL.md:613-615 still describes the sub-agent's return as "a JSON object with score, level, rationale, and optional signal arrays" — it was not updated for tier1_score / risk_floor, even though the fallback text 12 lines below it was.

Suggestion: reword the lines 51-54 intro so the table reads as the rubric the script implements — e.g. "risk-tier1.sh scores these dimensions and emits the composite as TIER1_SCORE; the table below documents the rubric it applies, and is used directly only when TIER1_SCORE is UNKNOWN" — rather than deleting the table, since procedure step 2's UNKNOWN branch still needs it. Also update skills/pr-review/SKILL.md:613-615 to list tier1_score and risk_floor in the parsed payload.

Comment thread scripts/post-review-test.sh Outdated
"gh label create risk/low"
run_risk_comment_test "risk-bad-provenance-not-rendered" \
"${RISK_BAD_PROVENANCE}" \
"**Risk Assessment: low (1/5)**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — the two new "garbage dropped" / "legacy unchanged" assertions are substring matches that pass on the very output they are meant to reject

Verified at head e6026cd; all lines below are inside the added hunk @@ -1776,6 +1783,97 @@.

1. The trailing-newline patterns collapse to plain substring matches. The assertions at 1866-1869 and 1872-1875 pass a pattern whose last character is a newline to grep -qF inside run_risk_comment_test (helper at 1789-1826). grep strips the trailing newline, so the check degrades to a plain substring match. Reproduced:

printf '**Risk Assessment: low (1/5)** · tier 1: 9;rm -rf\n' | grep -qF '**Risk Assessment: low (1/5)**
'   # matches

The RISK_BAD_PROVENANCE fixture at 1862 carries tier1_score: "9;rm -rf", so the test named for proving garbage provenance is dropped would still pass if that garbage were rendered. It asserts nothing about dropping.

2. The "legacy results unchanged" case at 1872 is not actually unchanged. It uses RISK_LOW_RESULT, whose head_sha is hex and matches the RISK_HEAD regex ^[0-9a-f]{6,40}$ at post-review.src.sh:482 — so that path now also emits a <summary>History</summary> block. The substring assertion cannot see that change.

3. Five tests ship literal placeholder names: "risk-f..." at 1842 and 1848, "risk-b..." at 1863 and 1866, "risk-l..." at 1872. Both helpers derive run_dir="${TMPDIR}/run-${test_name}" and stdout-${test_name}.log, so each duplicated pair shares a working directory and log file, and the PASS/FAIL lines are ambiguous.

Suggestion: replace the newline-terminated grep -qF patterns with an anchored check — grep -qxF '**Risk Assessment: low (1/5)**' or grep -qE '^\*\*Risk Assessment: low \(1/5\)\*\*$' — so appended meta fails the test, and add explicit negative assertions that tier 1: and degraded: are absent for RISK_BAD_PROVENANCE. Give all five tests unique descriptive names. Add an assertion for whether a legacy result should now get a History block, and fix either the code or the PR description to match.

The scorer makes tier1_score and risk_floor required in the risk
assessment, and the orchestrator fallback emits degraded. But
review-result.schema.json set additionalProperties:false on
risk_assessment, so a compliant result failed validation and the
whole review was dropped by the max_iterations:1 validation loop.

Add the three fields as optional (number 1-5, integer 1-5, and the
tier1-only enum) so legacy and UNKNOWN results stay valid, and add
schema tests using post-review-test.sh's own fixtures so the strict
schema and the lenient post-script cannot drift apart again.

Signed-off-by: guy oron <goron@redhat.com>
…n tests

Three review follow-ups on the risk-hardening PR:

- Labels: a degraded (tier-1-only fallback) score produced a risk/level
  label byte-identical to a computed one, so no consumer could honour
  the "treat degraded as no score" contract the PR documents. Apply a
  risk/degraded marker label alongside the level, and sweep it with the
  other stale risk labels so it clears when a later review is computed.

- Docs: the Tier 1 section intro still told the LLM to assign sub-scores
  and average them, contradicting the new "take TIER1_SCORE, do not
  re-derive" procedure — the exact split that caused the flip this PR
  fixes. Reword the intro as the rubric the script implements, used
  directly only when TIER1_SCORE is UNKNOWN. Also list tier1_score and
  risk_floor in pr-review's parsed-payload description.

- Tests: the garbage-dropped and legacy assertions used newline-
  terminated grep -qF, which strips the newline and degrades to a
  substring match that passed on the output it meant to reject. Add
  line/absent match modes, assert tier 1 and degraded meta are absent
  for bad provenance, and cover the degraded marker label and the
  legacy history row.

Signed-off-by: guy oron <goron@redhat.com>
@guyoron1

Copy link
Copy Markdown
Author

@waynesun09 thanks — all four addressed. Pushed 829e767 (schema) and 19aeaba (the three mediums).

  • CRITICAL (schema rejects the new fields): added tier1_score, risk_floor, and degraded to risk_assessment in schemas/review-result.schema.json, all optional so legacy and UNKNOWN results stay valid. Mirrored the fixtures from post-review-test.sh into validate-output-schema-test.sh (floored + degraded pass, bad-provenance rejected) so the strict schema and the lenient post-script can't drift again. 829e767.
  • MEDIUM (degraded never consulted in labelling): apply a risk/degraded marker label alongside the level when degraded is set, and add it to the stale-label sweep so it clears on a later computed review. Consumers that gate on risk can now treat it as "no score". Added label tests for both branches. 19aeaba.
  • MEDIUM (contradictory Tier 1 docs): reworded the section intro so the table reads as the rubric the script implements, used directly only when TIER1_SCORE is UNKNOWN; kept the "do not re-derive" procedure. Also listed tier1_score/risk_floor in pr-review/SKILL.md's parsed-payload description. 19aeaba.
  • MEDIUM (substring assertions pass on rejected output): the newline-terminated grep -qF collapsed to a substring match. Added line (whole-line) and absent match modes, assert tier 1:/degraded: are absent for bad provenance, and — you're right the legacy case isn't "unchanged" — renamed it and added an explicit legacy history-row assertion (- | cells). 19aeaba.

Names were already unique in the current file (the risk-f… you saw was display truncation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants