Skip to content

feat(workflows): adversarial-verification mean+veto rewire (V2, #2487) - #2511

Closed
flora131 wants to merge 13 commits into
verifier/criteria-modulefrom
verifier/adversarial-mean-veto
Closed

feat(workflows): adversarial-verification mean+veto rewire (V2, #2487)#2511
flora131 wants to merge 13 commits into
verifier/criteria-modulefrom
verifier/adversarial-mean-veto

Conversation

@flora131

@flora131 flora131 commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Slice V2 of the LLM-as-a-Verifier adoption program (specs/2026-08-17-verification-criteria-module.md §5.2): the adversarial-verification builtin moves from binary unanimity to per-criterion graded scoring with a deterministic mean+veto gate.

  • New optional inputs: criteria (record or criteria.md markdown; default task_fit/evidence/completeness), accept_mean (default 14), reask_limit (default 1)
  • Verifier fan-out is now criteria.length × verifier_count single-criterion stages scoring 1–20 on VERIFICATION_SCALE.schema with evidence[] and severity-tagged findings[]
  • INVALID_VERIFIER_REPORT deleted: an unparseable report persists as {invalid: true}, gets one bounded re-ask, and is never a counted vote (kills the workflows: adversarial-verification unanimity gate degrades as verifier_count grows; aggregate instead of AND #2255 bug class)
  • decide_verification (from verification-criteria.ts) is the sole accept path — quorum ∧ mean ≥ accept_mean ∧ no veto; the reducer is demoted to findings-consolidator and cannot flip the decision; Indeterminate rounds repeat once then end not-approved with quorum-failure evidence
  • Outputs reshaped (breaking, per program posture): approved, mean_score, per-criterion score table path, repairs_completed, candidate_path, review_report_path, remaining_work

Base: verifier/criteria-module (V1, #2504). Reconciles with V3's prompt-layout wiring at V6 as planned.

Evidence

Produced by an implement→review→repair goal run (2 turns; completion/evidence/risk reviewers all returned complete on the final checkout; remaining_work: none):

  • npm run check — green (includes the coding-agent tsgo erasableSyntaxOnly pass)
  • npx vitest --run --project unit -t "adversarial" — green: per-criterion artifacts, invalid-report fixture re-asked once and never counted as a vote, veto finding forces repair regardless of mean, quorum failure ends not-approved
  • Size cap: 494 changed source lines (< 500; packages/** only; tests uncapped — 649 total insertions across 9 files)
  • CHANGELOG: packages/workflows/CHANGELOG.md under ## [Unreleased] — ### Breaking Changes + ### Added

Spec contract: specs/2026-08-17-verification-criteria-module.md §5.2 (Q1: deterministic gate owns the accept; Q2: 3 criteria, accept_mean 14).


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Greptile Summary

This change introduces configurable per-criterion adversarial verification with deterministic acceptance and retry handling. Execution confirmed that the documented 1–20 acceptance scale is not enforced, so callers can approve low-scoring non-vetoed results with a zero threshold or reject perfect results with a threshold above the scale. Execution also confirmed that a large retry setting repeatedly schedules invalid verifier work and can create hundreds of model calls from one workflow run. The contract-test helper also needs specific schema types rather than unknown casts.

Confidence Score: 1/5

Do not merge until the acceptance and retry inputs are bounded and the generated contract test is brought into compliance with the repository typing rule.

The acceptance-threshold and retry-amplification failures were reproduced through focused executions of the real workflow path with uploaded source and output evidence. These are independent externally controlled behaviors with security consequences.

Files Needing Attention: packages/workflows/builtin/adversarial-verification.ts needs bounds for accept_mean and reask_limit; packages/workflows/builtin/adversarial-verification-runner.ts should defensively cap retries; test/unit/builtin-workflows-adversarial-generate.test.ts needs specific helper types and boundary assertions.

Security Review

Two reproduced issues affect the verification control boundary. An invocation can lower the acceptance threshold to bypass score-based rejection for non-vetoed results, and an unbounded retry input can induce disproportionate model usage, runtime, and cost through repeated invalid-report waves.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced a proof for a posted P1 finding that includes the reproduction test and before/after logs.
  • T-Rex produced a proof for a posted P1 finding with the focused reask-limit harness and accompanying before/after logs plus a focused indeterminate-round test log.
  • T-Rex produced a proof for a posted P1 finding with no additional artifacts attached.
  • T-Rex performed general contract validation by reproducing the workflow path and describing how score gates affect approval and rejection under the security model.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Unbounded accept_mean lets callers bypass or make impossible the 1–20 verification threshold

    • Bug
      • The TypeBox schema accepts accept_mean values outside its documented 1–20 verification scale, and the runner forwards that value unchanged into the acceptance policy.
    • Cause
      • packages/workflows/builtin/adversarial-verification.ts:21 uses Type.Number with a default but no minimum or maximum; packages/workflows/builtin/adversarial-verification-runner.ts:224,299 passes the supplied value to decide_verification, which compares it directly against the mean.
    • Fix
      • Declare accept_mean with minimum: 1 and maximum: 20 (and use Type.Integer too if fractional thresholds are not intended), then change the contract test to require Value.Check(..., 0) and Value.Check(..., 21) to be false.

    T-Rex Ran code and verified through T-Rex

Prompt To Fix All With AI
### Issue 1
packages/workflows/builtin/adversarial-verification.ts:21
**Out-of-scale acceptance threshold**

`accept_mean` accepts values outside the documented 1–20 verification scale and the runner forwards the value directly to the deterministic gate. A caller can set it to `0` to approve a full-quorum, non-vetoed result regardless of how low its score is, or set it above `20` to make even perfect reports fail. Constrain this input to the supported scale.

### Issue 2
packages/workflows/builtin/adversarial-verification.ts:22
**Unbounded verifier re-ask waves**

`reask_limit` has no upper bound and directly controls repeated parallel verifier waves for invalid reports. A large invocation value causes each invalid cell to be retried repeatedly before the workflow returns an indeterminate result, consuming excessive model calls, runtime, and cost. Apply a conservative maximum in the schema and enforce the same bound in the runner for direct callers.

### Issue 3
test/unit/builtin-workflows-adversarial-generate.test.ts:49-50
**Ambiguous schema helper types**

The new `schemaShape` helper casts both its input and record values through `unknown`, contrary to the repository requirement to use specific types and at the cost of losing useful schema diagnostics in these contract tests.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(workflows): drop unused adversarial ..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Context used:

  • Context used - AGENTS.md (source)

@flora131
flora131 marked this pull request as ready for review August 19, 2026 16:53
@flora131
flora131 deleted the branch verifier/criteria-module August 19, 2026 16:54
@flora131 flora131 closed this Aug 19, 2026
Type.String(),
Type.Record(Type.String(), Type.String()),
], { default: DEFAULT_CRITERIA, description: "Criteria record of name-to-description entries, or criteria.md markdown." }),
accept_mean: Type.Number({ default: 14, description: "Mean score required for acceptance on the 1–20 verification scale." }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Out-of-scale acceptance threshold

accept_mean accepts values outside the documented 1–20 verification scale and the runner forwards the value directly to the deterministic gate. A caller can set it to 0 to approve a full-quorum, non-vetoed result regardless of how low its score is, or set it above 20 to make even perfect reports fail. Constrain this input to the supported scale.

Artifacts

trex-artifacts/accept-mean-boundary-repro.test.ts

  • Authored focused Vitest reproduction checks the TypeBox boundary values and executes the actual workflow with valid score-1 and score-20 verifier reports, proving the vulnerable threshold behavior.

trex-artifacts/accept-mean-boundary-01-before.log

  • Observed pre-fix Vitest execution completed successfully against the unbounded schema while the reproduction assertions verified acceptance at zero and rejection at twenty-one, confirming the bug.

trex-artifacts/accept-mean-boundary-02-after.log

  • Observed temporary bounded-schema Vitest execution completed successfully with the test requiring zero and twenty-one to be rejected, demonstrating that schema bounds prevent the issue.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/workflows/builtin/adversarial-verification.ts
Line: 21

Comment:
**Out-of-scale acceptance threshold**

`accept_mean` accepts values outside the documented 1–20 verification scale and the runner forwards the value directly to the deterministic gate. A caller can set it to `0` to approve a full-quorum, non-vetoed result regardless of how low its score is, or set it above `20` to make even perfect reports fail. Constrain this input to the supported scale.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Type.Record(Type.String(), Type.String()),
], { default: DEFAULT_CRITERIA, description: "Criteria record of name-to-description entries, or criteria.md markdown." }),
accept_mean: Type.Number({ default: 14, description: "Mean score required for acceptance on the 1–20 verification scale." }),
reask_limit: Type.Integer({ minimum: 0, default: 1, description: "Maximum bounded re-ask waves for invalid criterion reports." }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Unbounded verifier re-ask waves

reask_limit has no upper bound and directly controls repeated parallel verifier waves for invalid reports. A large invocation value causes each invalid cell to be retried repeatedly before the workflow returns an indeterminate result, consuming excessive model calls, runtime, and cost. Apply a conservative maximum in the schema and enforce the same bound in the runner for direct callers.

Artifacts

trex-artifacts/reask-limit-harness.ts — Focused reask-limit runner harness

  • Authored Bun TypeScript harness supplies invalid verifier reports, validates the schema, and records retry waves and calls; takeaway: it directly exercises the claimed path.

trex-artifacts/reask-limit-01-before.log — Baseline invalid-verifier run with reask limit one

  • Executed `bun trex-artifacts/reask-limit-harness.ts 1` successfully and recorded schema acceptance, four waves, and twenty verifier calls; takeaway: this establishes the bounded baseline.

trex-artifacts/reask-limit-02-after.log — Large reask-limit invalid-verifier run

  • Executed `bun trex-artifacts/reask-limit-harness.ts 50` successfully and recorded accepted input, 102 waves, and 510 verifier calls; takeaway: this confirms uncontrolled amplification.

trex-artifacts/reask-limit-focused-test.log — Focused existing indeterminate-round test

  • Executed the repository's indeterminate-round unit test successfully; takeaway: it confirms the second indeterminate round that doubles retry waves.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/workflows/builtin/adversarial-verification.ts
Line: 22

Comment:
**Unbounded verifier re-ask waves**

`reask_limit` has no upper bound and directly controls repeated parallel verifier waves for invalid reports. A large invocation value causes each invalid cell to be retried repeatedly before the workflow returns an indeterminate result, consuming excessive model calls, runtime, and cost. Apply a conservative maximum in the schema and enforce the same bound in the runner for direct callers.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +49 to +50
function schemaShape(schema: unknown): Record<string, unknown> {
return schema as Record<string, unknown>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Ambiguous schema helper types

The new schemaShape helper casts both its input and record values through unknown, contrary to the repository requirement to use specific types and at the cost of losing useful schema diagnostics in these contract tests.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: test/unit/builtin-workflows-adversarial-generate.test.ts
Line: 49-50

Comment:
**Ambiguous schema helper types**

The new `schemaShape` helper casts both its input and record values through `unknown`, contrary to the repository requirement to use specific types and at the cost of losing useful schema diagnostics in these contract tests.

**Context Used:** AGENTS.md ([source](https://github.com/bastani-inc/atomic/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

1 participant