Skip to content

fix(agent): make "verified" unearnable without verification - #30

Merged
tcballard merged 3 commits into
mainfrom
claude/verified-semantics
Jul 2, 2026
Merged

fix(agent): make "verified" unearnable without verification#30
tcballard merged 3 commits into
mainfrom
claude/verified-semantics

Conversation

@tcballard

@tcballard tcballard commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Batch 2 of the technical-review fixes. Stacked on #29 (both touch the drive loop) — merge #29 first; GitHub will retarget this to main automatically.

Four gaps let the product promise — the word "verified" — be claimed without being earned. Each is closed at its narrowest choke point.

What

  • Finish is explicit. finished = response.done !== undefined was vacuously true (both adapters always set done when there are no tool calls), so a model that gave up scored as success and never reached failure-learning. Completion is now only an explicit finish tool call; DriveResult carries stopReason (finished / gave_up / step_budget) plus the model's parting text, and give-ups are recorded to the learning store with distinct reasons.
  • Assertions are required. A zero-assertion session compiled to a trivially-green spec and passed the fidelity gate. runQa now reports it unverified without compiling (QaResult.loop is optional; unverifiedReason says why), and emitSpec refuses assertion-free sessions the way it already refused empty ones.
  • Exact locators, both sides. Emitted getByRole/getByText/getByLabel matched by substring, case-insensitive — a later DOM addition could silently re-target a committed test. Now { exact: true } in the emitter and the Recorder (record/replay agreement is the invariant, changed together), with locator guidance telling the model to copy names verbatim.
  • Contract edges refuse, don't guess. A numeric schema_version coerced to "" and bypassed the compatibility guard as "omitted" — now stringified and checked (2 refused, 1 accepted). An empty Playwright report mapped to a misleading "failed"/"unstable" quarantine — now a ReportParseError naming the likely cause (spec outside the config's testDir). A target project's configured retries inverted run semantics (flake-then-pass read as failed) — now each test's final attempt is its outcome.

New default-CI coverage

tests/drive-loop.test.ts unit-tests the loop's control logic with a fake page + scripted models — give-up vs finish vs step budget, tool withholding, refusal feedback. This logic previously ran only behind PROOFKEEPER_E2E, which is exactly where the finished-tautology bug lived.

Corpus (dogfood)

req-verified-semantics + design-verified-semantics, roadmap initiative, ## Verified By → the four pinning test files. Gates green: rac validate (31/31), relationships 45/45 (0 issues), review clean, self-coverage 15/15.

Verification

  • Unit: 284 passed (23 new/updated).
  • Real browser (PROOFKEEPER_E2E=1): all 7 integration suites pass — record→emit→fidelity stays green with exact locators, extension drive included.
  • npm run typecheck / build green.

tcballard added 3 commits July 1, 2026 22:14
…autonomous-qa-enhancements]

Four gaps let the product promise be claimed without being earned:

- finished was inferred from a field both adapters always set, so a
  give-up scored as success. Completion is now only an explicit finish
  tool call; DriveResult carries stopReason (finished/gave_up/
  step_budget) and the model's parting text, and give-ups reach the
  failure-learning store.
- an assertion-free session compiled to a trivially-green spec and
  passed the gate. runQa now reports it unverified without compiling,
  and emitSpec refuses such sessions outright.
- emitted role/text/label locators matched by substring; they are now
  exact on both record and replay (recorder and emitter change
  together), and the locator guidance says to copy names verbatim.
- a numeric graph schema_version bypassed the compatibility guard as
  'omitted' (now stringified and checked), an empty Playwright report
  mapped to a misleading 'failed' (now a diagnostic naming testDir),
  and configured retries inverted run semantics (now each test's final
  attempt is its outcome).

Signed-off-by: Tom Ballard <tom@armytage.co>
…ap:autonomous-qa-enhancements]

Adds tests/drive-loop.test.ts — the loop's control logic (give-up vs
finish vs step budget, tool withholding, dispatch refusal feedback)
unit-tested with a fake page and scripted models, so the semantics the
product promise rests on run in default CI, not only behind
PROOFKEEPER_E2E. Pins the emitter's assertion-free refusal and exact
locators, the numeric schema_version guard, final-attempt retry
reduction, and the no-tests-matched diagnostic; drive doubles now
record an assertion and a stopReason.

Signed-off-by: Tom Ballard <tom@armytage.co>
…oadmap:autonomous-qa-enhancements]

Implements lore-proofkeeper/requirements/req-verified-semantics.md.

Adds the requirement and design artifacts, the roadmap initiative, and
the changelog entry.

Signed-off-by: Tom Ballard <tom@armytage.co>
@tcballard
tcballard changed the base branch from claude/trust-boundary to main July 2, 2026 04:38
@tcballard
tcballard merged commit 206f556 into main Jul 2, 2026
8 checks passed
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