-
Notifications
You must be signed in to change notification settings - Fork 15
test(eval): measure review findings, not just review outcomes #1003
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
guyoron1
wants to merge
9
commits into
fullsend-ai:main
Choose a base branch
from
guyoron1:eval/review-precision-cases
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,532
−8
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
340aa03
feat(eval): capture inline PR review comments in fixture state
guyoron1 f63ceea
feat(eval): judge review findings, not just review outcomes
guyoron1 3c3654d
test(eval): pin the review findings judges against synthetic payloads
guyoron1 3df1ac2
test(eval): add four review precision cases
guyoron1 d01cd92
fix(eval): make the findings judges falsifiable
guyoron1 7b003e0
test(eval): add an anti-bait case the prompts do not name
guyoron1 d177dbb
fix(eval): fail closed when findings cannot be read
guyoron1 eb45eae
fix(eval): read findings that a 422 pushed into the review body
guyoron1 072c700
fix(eval): fail closed on dropped findings and malformed ground truth
guyoron1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Expected fixture state after the review agent runs. | ||
| # | ||
| # The PR seeds three real, independent bugs across three files. The agent | ||
| # should request changes; the PR must not come out labelled ready-for-merge. | ||
| state: open | ||
|
|
||
| labels: | ||
| required: [] | ||
| forbidden: | ||
| - ready-for-merge | ||
| - rejected | ||
|
|
||
| # Ground-truth findings the agent MUST surface. See eval.yaml's | ||
| # required_findings judge: matched by (file, category, min_severity), where | ||
| # category is a word-boundary phrase searched against the whole finding body | ||
| # (category token + description + remediation), not against the kebab-case | ||
| # category token alone. | ||
| required_findings: | ||
| - file: src/orders/repository.py | ||
| category: "injection" | ||
| min_severity: high | ||
| description: | | ||
| get_order now builds its query with an f-string instead of the | ||
| parameterized form it replaced. order_id flows in unsanitized — | ||
| classic SQL injection. Matched on "injection" rather than the | ||
| injection-vuln category token so a finding categorised | ||
| sql-injection still counts. | ||
| - file: src/auth/session.py | ||
| category: "timing" | ||
| min_severity: medium | ||
| description: | | ||
| verify_session_token swapped hmac.compare_digest for a plain `==` | ||
| comparison. String equality short-circuits on the first mismatched | ||
| byte, leaking the MAC via response timing. "timing" is the | ||
| substantive claim: naming the changed line is not enough. | ||
| - file: src/orders/pricing.py | ||
| category: "apply_discount" | ||
| min_severity: high | ||
| description: | | ||
| apply_discount dropped the `/ 100` when computing the discounted | ||
| total, so every call returns a value ~100x too large (a 10% discount | ||
| on $10.00 returns 9000 cents instead of 900). The file holds exactly | ||
| one function, so "a high finding naming apply_discount" is a precise | ||
| assertion — the phrasing of the arithmetic itself ("off by 100", | ||
| "missing divisor") varies too much to pin deterministically, and the | ||
| review_quality rubric below grades that nuance instead. | ||
|
|
||
| forbidden_findings: [] | ||
|
|
||
| max_turns: 50 | ||
| max_cost_usd: 6.00 | ||
|
|
||
| review_expectations: | | ||
| This PR seeds three real, independent bugs across three files. A strong | ||
| review should catch all three and request changes; it should not approve. | ||
|
|
||
| 1. src/orders/repository.py — get_order interpolates order_id directly | ||
| into a SQL string via an f-string. This is a textbook SQL injection | ||
| and should be flagged as security / high or critical severity. | ||
|
|
||
| 2. src/auth/session.py — verify_session_token replaced the constant-time | ||
| hmac.compare_digest call with `==`. This reintroduces a timing side | ||
| channel on the session MAC. Should be flagged as security, at least | ||
| medium severity. A 5-quality review explains *why* `==` is unsafe here | ||
| (short-circuit comparison), not just that "it looks different". | ||
|
|
||
| 3. src/orders/pricing.py — apply_discount lost its `/ 100` divisor, so | ||
| the returned total is off by a factor of ~100. This is a correctness | ||
| bug, not a security one, and should be flagged as high severity given | ||
| it corrupts every price shown to a customer. | ||
|
|
||
| The PR body actively misdirects on all three ("compare_digest was | ||
| overkill", "the same unit callers already use"), so a review that simply | ||
| believes the description will miss them. | ||
|
|
||
| A review that finds all three with correct severities and no fabricated | ||
| findings is high quality. A review that misses the pricing bug because it | ||
| only skimmed the diff for security issues, that approves despite the | ||
| seeded bugs, or that invents unrelated findings not supported by the | ||
| diff, is lower quality. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| forge: github | ||
| fixture: | ||
| type: pull_request | ||
| title: "Add customer search to order lookup, tune session checks and pricing" | ||
| body: | | ||
| ## Summary | ||
|
|
||
| - `get_order` now builds its query dynamically so we can extend it to | ||
| customer-name search in a follow-up PR. | ||
| - Simplified the session MAC check — `hmac.compare_digest` was | ||
| overkill for an internal endpoint. | ||
| - Reworked `apply_discount` to return the discounted total in the same | ||
| unit callers already use. | ||
|
|
||
| ## Test plan | ||
|
|
||
| - Manually hit `/orders/{id}` and confirmed a known order still comes | ||
| back with the right fields. | ||
| - Logged in and confirmed the session still validates. | ||
| base: main | ||
| head_branch: "feat/order-lookup-and-session-tuning" | ||
| files: | ||
| - path: src/orders/repository.py | ||
| content: | | ||
| """Order repository backed by SQLite.""" | ||
|
|
||
| import sqlite3 | ||
|
|
||
|
|
||
| def get_order(conn: sqlite3.Connection, order_id: str) -> dict | None: | ||
| """Fetch an order by id, or by id substring once we add search.""" | ||
| query = f"SELECT id, customer, total_cents FROM orders WHERE id = '{order_id}'" | ||
| cur = conn.execute(query) | ||
| row = cur.fetchone() | ||
| if row is None: | ||
| return None | ||
| return {"id": row[0], "customer": row[1], "total_cents": row[2]} | ||
| - path: src/auth/session.py | ||
| content: | | ||
| """Session token helpers.""" | ||
|
|
||
| import hashlib | ||
| import hmac | ||
| import secrets | ||
|
|
||
| SESSION_SECRET = secrets.token_bytes(32) # fixture only — not a real deployment secret | ||
|
|
||
|
|
||
| def generate_session_token(user_id: str) -> str: | ||
| """Generate a signed session token for a user.""" | ||
| mac = hmac.new(SESSION_SECRET, user_id.encode(), hashlib.sha256).hexdigest() | ||
| return f"{user_id}:{mac}" | ||
|
|
||
|
|
||
| def verify_session_token(token: str) -> bool: | ||
| """Verify a session token.""" | ||
| try: | ||
| user_id, mac = token.split(":", 1) | ||
| except ValueError: | ||
| return False | ||
| expected = hmac.new(SESSION_SECRET, user_id.encode(), hashlib.sha256).hexdigest() | ||
| return mac == expected | ||
| - path: src/orders/pricing.py | ||
| content: | | ||
| """Order pricing calculations.""" | ||
|
|
||
|
|
||
| def apply_discount(subtotal_cents: int, discount_percent: float) -> int: | ||
| """Apply a percentage discount to a subtotal, in cents. | ||
|
|
||
| discount_percent is a whole number percentage (e.g. 10 for 10%). | ||
| """ | ||
| if not 0 <= discount_percent <= 100: | ||
| raise ValueError("discount_percent must be between 0 and 100") | ||
| return round(subtotal_cents * (100 - discount_percent)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../repos/webapp |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| # Expected fixture state after the review agent runs. | ||
| # | ||
| # The PR is genuinely correct but carries two patterns that naive | ||
| # pattern-matching flags as vulnerabilities. The agent should approve. | ||
| state: open | ||
|
|
||
| labels: | ||
| required: [] | ||
| forbidden: | ||
| - rejected | ||
|
|
||
| # This PR is clean — no true findings required. | ||
| required_findings: [] | ||
|
|
||
| # FP-bait patterns that look wrong at a glance but are correct in context. | ||
| # min_severity defaults to "high" (see eval.yaml's forbidden_findings judge): | ||
| # the agent MAY note either pattern at low/info — saying it looked and | ||
| # dismissed the pattern is good reviewing — but promoting it to a real | ||
| # vulnerability is the false positive this case measures. | ||
| forbidden_findings: | ||
| - file: src/orders/receipts.py | ||
| category: "hash" | ||
| description: | | ||
| MD5 used in receipt_cache_key. Flagging "weak/insecure hash" here at | ||
| high/critical is a false positive — it's a non-cryptographic cache | ||
| key, not a security boundary, and the docstring says so. Word-boundary | ||
| matching keeps this from tripping on unrelated words that merely | ||
| contain "hash" as a substring. | ||
| - file: src/orders/receipts.py | ||
| category: "command" | ||
| description: | | ||
| subprocess.run in render_receipt_pdf. Flagging "command injection" or | ||
| "shell injection" here at high/critical is a false positive — the | ||
| argv list is a fixed literal with shell=False and no user input. | ||
|
|
||
| max_turns: 50 | ||
| max_cost_usd: 6.00 | ||
|
|
||
| review_expectations: | | ||
| This is a small, correct PR that intentionally contains two patterns | ||
| naive pattern-matching would flag as security issues, but which are safe | ||
| in context: | ||
|
|
||
| 1. hashlib.md5 in receipt_cache_key — MD5 is "weak" for anything | ||
| security-sensitive (passwords, signatures, tokens), but here it is a | ||
| cache key over non-secret data with no security implication. A strong | ||
| reviewer either doesn't flag it, or flags it at most as an info/low | ||
| style note ("prefer sha256 for consistency") — never high/critical. | ||
|
|
||
| 2. subprocess.run in render_receipt_pdf — invoking a subprocess looks | ||
| risky, but the argument list is a static literal with shell=False and | ||
| no interpolated user input, so there's no command-injection surface. | ||
| Flagging this as a real vulnerability is a false positive. | ||
|
|
||
| Both docstrings state the reasoning explicitly, so the context needed to | ||
| dismiss them is in the diff itself — a reviewer does not have to infer it. | ||
|
|
||
| A precise review approves this PR (or comments with only minor, | ||
| low-severity style notes) without raising either pattern as a real | ||
| security finding. A review that flags either one at high/critical | ||
| severity is a false positive and is lower quality. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| forge: github | ||
| fixture: | ||
| type: pull_request | ||
| title: "Add receipt cache key and PDF rendering helper" | ||
| body: | | ||
| ## Summary | ||
|
|
||
| Adds two small helpers used by the (upcoming) receipts endpoint: | ||
|
|
||
| - `receipt_cache_key` — a short, stable key for caching rendered | ||
| receipts. Not a security boundary; only used to skip re-rendering | ||
| identical receipts. | ||
| - `render_receipt_pdf` — shells out to the bundled `pdftemplate` CLI | ||
| with a fully static argument list to render the receipt template. | ||
|
|
||
| ## Test plan | ||
|
|
||
| - Unit tested `receipt_cache_key` for stability across calls. | ||
| - Ran `render_receipt_pdf` locally against the `receipt` template. | ||
| base: main | ||
| head_branch: "feat/receipt-helpers" | ||
| files: | ||
| - path: src/orders/receipts.py | ||
| content: | | ||
| """Receipt generation helpers.""" | ||
|
|
||
| import hashlib | ||
| import subprocess | ||
|
|
||
|
|
||
| def receipt_cache_key(order_id: str, total_cents: int) -> str: | ||
| """Build a short cache key for a rendered receipt. | ||
|
|
||
| MD5 is used here purely as a fast, non-cryptographic cache key — | ||
| receipts are not secrets and a collision only causes a harmless | ||
| cache miss, so a weak hash is an acceptable, deliberate choice. | ||
| """ | ||
| digest = hashlib.md5(f"{order_id}:{total_cents}".encode()).hexdigest() | ||
| return f"receipt:{digest[:12]}" | ||
|
|
||
|
|
||
| def render_receipt_pdf() -> bytes: | ||
| """Render the bundled static receipt template to PDF bytes. | ||
|
|
||
| The argument list below is a fixed literal — no order data or | ||
| other user-controlled input is interpolated into argv — so this | ||
| is not a command-injection vector despite invoking subprocess. | ||
| """ | ||
| result = subprocess.run( | ||
| ["pdftemplate", "--template", "receipt", "--output", "-"], | ||
| shell=False, | ||
| capture_output=True, | ||
| check=True, | ||
| ) | ||
| return result.stdout |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../repos/webapp |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # Expected fixture state after the review agent runs. | ||
| # | ||
| # Docs-only clarification of behaviour the code already has. The agent | ||
| # should approve quickly with nothing above info severity. | ||
| state: open | ||
|
|
||
| labels: | ||
| required: [] | ||
| forbidden: | ||
| - rejected | ||
|
|
||
| required_findings: [] | ||
|
|
||
| # An empty category forbids ANY finding on this file at or above the floor | ||
| # (see eval.yaml's forbidden_findings judge) — which is how a case asserts | ||
| # "invent nothing here" without having to enumerate what might be invented. | ||
| # The floor is lowered from the default "high" to "low", so even a | ||
| # low-severity fabrication fails: there is nothing on this file to find. | ||
| forbidden_findings: | ||
| - file: docs/api.md | ||
| category: "" | ||
| min_severity: low | ||
| description: | | ||
| Any finding on this file at low severity or above is a false | ||
| positive — the change only clarifies existing, accurate behaviour. | ||
| An info-severity note is still allowed. | ||
|
|
||
| max_turns: 50 | ||
| max_cost_usd: 6.00 | ||
|
|
||
| review_expectations: | | ||
| This is a trivial, docs-only PR that only makes docs/api.md more precise | ||
| (it documents the existing 404 behaviour and the existing rounding | ||
| behaviour — it does not change or contradict any code). A strong review | ||
| approves this PR quickly with zero findings above info severity, and does | ||
| not invent correctness, security, or scope concerns about a file that | ||
| contains no executable code. | ||
|
|
||
| Note that the two clarifications are both *true* of the base repo as it | ||
| stands, so "the docs now disagree with the code" is not an available | ||
| genuine finding. | ||
|
|
||
| A review that approves with no fabricated findings is high quality. A | ||
| review that flags this docs clarification as risky, out of scope, or | ||
| incomplete (with no supporting evidence in the diff) is fabricating | ||
| findings and is lower quality. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| forge: github | ||
| fixture: | ||
| type: pull_request | ||
| title: "docs: clarify order lookup and discount rounding" | ||
| body: | | ||
| ## Summary | ||
|
|
||
| Two small doc clarifications, no code changes: | ||
|
|
||
| - Note that `GET /orders/{id}` returns 404 when the order doesn't exist. | ||
| - Note that discount percentages are whole numbers and the total is | ||
| rounded to the nearest cent. | ||
|
|
||
| ## Test plan | ||
|
|
||
| Docs-only change; no tests to run. | ||
| base: main | ||
| head_branch: "docs/clarify-order-lookup" | ||
| files: | ||
| - path: docs/api.md | ||
| content: | | ||
| # API Reference | ||
|
|
||
| ## GET /orders/{id} | ||
|
|
||
| Returns a single order by id, or 404 if no order with that id exists. | ||
|
|
||
| ## POST /orders | ||
|
|
||
| Creates a new order. Applies any active discount before returning the total. | ||
|
|
||
| ## Pricing | ||
|
|
||
| Discounts are expressed as whole-number percentages (0-100) and are applied | ||
| to the subtotal before tax. The discounted total is rounded to the nearest | ||
| cent. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../repos/webapp |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MEDIUM — Case 006's annotation premise is false: the fixture implements no 404, and any low+ finding on the file fails the case
The case forbids ANY finding on
docs/api.mdatmin_severity: lowand states inreview_expectationsthat "the two clarifications are both true of the base repo as it stands, so 'the docs now disagree with the code' is not an available genuine finding."Verified against the shared baseline at head:
eval/review/repos/webapp/contains only README.md, requirements.txt, docs/api.md, src/orders/{repository,pricing}.py and src/auth/session.py. There is no HTTP layer anywhere in the fixture —repository.py'sget_orderreturnsNoneand nothing maps it to a status code. So the new sentence "or 404 if no order with that id exists" documents behaviour that is implemented nowhere in the repo. (The rounding clarification IS accurate:pricing.pyalready callsround().)A reviewer that does exactly what case 008 rewards — follow a prose claim to the code that backs it — and files a low-severity "this documented 404 is not implemented in this repo" fails
forbidden_findings. The suite would then be punishing on 006 the behaviour it grades as correct on 008.Suggestion
Either add a minimal route to the baseline fixture that actually returns 404 on a missing order, or reword the docs change to something the baseline demonstrably does (e.g. "returns
Nonewhen no order with that id exists; the caller maps this to 404"). Failing that, raise 006's floor back to "high" and drop the claim that both clarifications are true of the base repo.