fix(corpus): CHAOS-5722 score any_of serve branches from the persisted semantic state - #75
Merged
Merged
Conversation
…state An any_of serve branch's family-confirmation link now reads the served result's persisted semantic state (acr M2 semantic_state column) through an injected persisted_semantic_state(result_id) adapter, instead of staying uniformly unscored/family_confirmation_unavailable while a wrongly-certified refuse branch on the same row can agree. Reuses the engine's own two closed absent/unreadable reason tokens for the new unscored reasons.
chrisgeo
force-pushed
the
fix/5722-anyof-serve-branch-scoring
branch
from
September 13, 2026 22:32
a952c27 to
9944712
Compare
…dence score_branch's window-binding gate is != "mismatch", not == "verified" -- an ordinary single-turn serve or a merely inconclusive window-clarification audit must not block a real, independently-grounded persisted-family link. That intent was implicit in the code and easy to misread as an oversight; this spells it out in score_branch's own docstring and adds executed coverage for both shapes (no exchange at all, and an exchange attempted but unreadable).
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Ticket: CHAOS-5722
TEST-EVIDENCE
What this change accomplishes, and for whom. The corpus yardstick harness scores each
any_ofrow's declared alternatives and reports the best-ranked one. A served, correct answer'sservebranch was capped atunscoredforever (no wire field could confirm it), while a wrongly-certifiedrefusebranch on the same row could reachagree— so the published numbers made a correct serve look worse than an incorrect refuse. This change lets aservebranch reachagreewhen the served result's own PERSISTED engine state (acr's M2semantic_statecolumn, read by an injected adapter — the scorer itself never opens a connection) confirms the family and a passed validation gate, so chris and the team reading yardstick output see a serve's real correctness instead of a policy artifact.Executed claim.
pnpm test:corpus(freshpnpm installin the lane worktree,corpus/test_semantic_verdict.pyrun standalone too) is GREEN at the pushed tip: 33/33semantic_verdictcontrols, 2/2 smoke controls over all 36 real corpus rows, 2/2 real vendored-fixture proof controls (9 reps ofcv-discovered-team-series, figures byte-identical to the pre-existing published totals for that fixture), 4/4 schema-driven shape controls. Command and output:Full ask-dev quality gate at the pushed tip, fresh
pnpm install:prettier --check .rc=0,pnpm lintrc=0,pnpm typecheckrc=0,pnpm test:unit(vitest) rc=0,NODE_ENV=production pnpm build(next build) rc=0.Surface touched.
corpus/semantic_verdict.py(newPersistedSemanticStateUnreadable,_score_persisted_family_confirmation,persisted_semantic_state=Noneparameter onscore_branch/score/build_verdict,POLICY_VERSIONbump),corpus/expect_schema.py(SEMANTIC_STATE_ABSENT/SEMANTIC_STATE_UNREADABLEread from the synced wire schema's ownsemantic_reading.reasonenum, not hand-copied),corpus/README.md(doc). No acr change, no wire/contract change, no corpus rowexpectvalue changed (D24's three realany_ofrows are untouched data).Red-first. Every new behavior is exercised by a test that is RED without this change:
corpus/test_semantic_verdict.pybefore this diff has nopersisted_semantic_stateparameter at all, so every new test below fails withTypeError: score() got an unexpected keyword argument(or, for the renamed default-path test, asserts the OLDfamily_confirmation_unavailablereason) on the pre-change source; green on this branch.Guard input-domain table (
_score_persisted_family_confirmation, the new validator this change adds — every cell executed intest_semantic_verdict.py, PASS in the run above):None)unscored/semantic_state_absenttest_family_match_is_not_confirmation_without_a_persisted_link,test_build_verdict_with_no_persisted_state_argument_is_backward_compatibleNone(no row)unscored/semantic_state_absenttest_persisted_state_absent_stays_unscoredPersistedSemanticStateUnreadableunscored/semantic_state_unreadabletest_persisted_state_unreadable_stays_unscoredunscored/semantic_state_unreadabletest_persisted_state_non_dict_return_is_unreadableresult_idunscored/semantic_state_absent, adapter never calledtest_malformed_result_id_is_absent_and_adapter_is_never_calledformat_versionagree/disagreetestformat_versionunscored/semantic_state_version_unknowntest_persisted_state_unknown_format_version_is_unscoredfamilyunscored/semantic_state_unreadabletest_persisted_state_malformed_shape_is_unreadablefamilydisagree/persisted_family_mismatchtest_persisted_family_mismatch_is_disagreevalidationunscored/semantic_state_unreadabletest_persisted_state_malformed_shape_is_unreadablegate_outcomeunscored/semantic_state_unreadabletest_persisted_state_malformed_shape_is_unreadablegate_outcomeunscored/gate_outcome_not_acceptedtest_persisted_gate_outcome_not_accepted_is_not_agree(sweepsrefused_basis/rejected_invalid/not_proposed/empty)gate_outcomeagree/family_confirmedtest_persisted_link_promotes_serve_to_agree_and_outranks_a_wrongly_certified_refuse,test_persisted_link_fields_are_published_in_branch_resultsmismatch(a POSITIVE, proven mistake in the window mechanics)disagreetest_adapter_is_never_called_when_window_binding_mismatches_or_family_changedunknownwith NO exchange at all (an ordinary single-turn serve)test_persisted_link_promotes_a_single_turn_serve_with_no_window_exchange_at_allunknownwith an exchange attempted but inconclusive (a corpus-capture gap, e.g. a missing confirmation record)test_persisted_link_promotes_a_serve_whose_window_exchange_audit_is_merely_inconclusivechangedtest_adapter_is_never_called_when_window_binding_mismatches_or_family_changedexpecttest_adapter_is_never_called_for_a_scalar_rowtest_adapter_is_never_called_for_a_refuse_branch_RANKorderingtest_rank_ordering_is_unchangedtest_scalar_row_is_untouched_by_the_new_machinery(pre-existing, still green),test_build_verdict_runs_over_every_real_corpus_rowWindow-binding gate is
!= "mismatch", deliberately not== "verified". The window audit (audit_window_exchange) and the persisted-family link are independent evidence about independent questions — the audit asks whether a window-clarification receipt was correctly offered and applied, the persisted link asks whether the engine's own stored record of what it served and validated matches what was declared. An ordinary single-turn serve never goes through a window-clarification exchange at all, so the audit's own default (window_binding="unknown",reason="unsupported_exchange") has nothing to confirm or deny about the persisted link's own question; only a POSITIVE, proven mistake in the window mechanics ("mismatch") contradicts it. Both an absent exchange and an inconclusive one (a two-turn exchange the corpus harness could not fully read back, e.g. a missing confirmation record) read as"unknown"and are exercised as separate, named cells in the domain table above — neither blocks a real, independently-grounded persisted link, and the real-worldneg-explicit-comparisonrow in the RESCORE table below is exactly this shape (a served row with no window-clarification exchange at all).Class sweep. The absent/unreadable-reason pair (
SEMANTIC_STATE_ABSENT/SEMANTIC_STATE_UNREADABLE) is read once from the synced wire schema'ssemantic_reading.reasonenum (expect_schema._read_semantic_reading_reasons, fails loudly at import if the enum ever stops being exactly these two members) and consumed from that single source everywhere this change needs it — there is no second hand-typed copy of the two tokens to drift.RESCORE (read-only, real trial-store data — numbers of record are unchanged; this is a proof-of-effect replay, not a landed rescore). Reproduced the three archived
any_ofrows named in the ticket across all 3 reps of the pinned proof at acr0af9fa84(~/.cache/acr-kiac-askdev/proofs/2026-09-13-main-0af9fa84/), reading each row'slast_result_idand re-running this branch'ssemantic_verdict.build_verdict()with apersisted_semantic_stateadapter backed by a REAL, read-only query against the k3s trial store:All 9 result rows carried
format_version=semantic-state.v1,gate_outcome=passed. The row's own served/refused observed family was read from the archived attempt artefacts (.../seq/shard-00/replicate/<id>-rep<n>-t*-a*.json), never re-derived. Corpus question TEXT is not reproduced anywhere below or in this repo — row ids only.agree/no_match: did not serve, as declaredunserved), so itsservebranch's own legacy verdict staysdisagreeand never reaches the persisted-link checkagree/no_match: did not serve, as declaredservebranch at all (refuse/clarifyonly)unscored/family_confirmation_unavailableagree/family_confirmedunscored/family_confirmation_unavailableagree/family_confirmedunscored/family_confirmation_unavailableagree/family_confirmed3 of 9 rescored rows changed. The two rows the ticket cited as "wrongly-certified refuse" are unaffected by this change exactly as designed (their
servebranch, where either present, never reaches the new check because it was never served); the one row that actually served and was wrongly capped now scores at its correctagree, resolving the asymmetry the ticket names.POLICY_VERSIONbumped toany_of-fail-closed-v2-persisted-family-linkso this rescore is never mistaken for a like-for-like comparison against av1run.SELF-REVIEW. Verified against the pushed tip: (1) every non-serve branch and every scalar/absent-
expectrow never invokes the adapter (call-count doubles, above) — the M2 read is strictly additive to the serve-branch path; (2) every pre-existing test intest_semantic_verdict.py/test_semantic_verdict_smoke.py/test_semantic_verdict_proof.pystill passes unmodified except the one test whose assertion this ticket's own behavior change targets (test_family_match_is_not_confirmation_without_a_persisted_link, renamed fromtest_family_match_is_not_confirmation, reason string updated from the pre-change token to the D49 token — same verdict tier,unscored, both before and after); (3)build_verdict/score/score_branchall keeppersisted_semantic_stateas a trailing optional parameter after every pre-existing positional argument, so acr's unmodifiedscripts/corpus/semantic_verdict_bridge.py(which this ticket does not touch) keeps callingbuild_verdict()exactly as it does today, scoring everyany_ofserve branch asunscored/semantic_state_absent(same rank as before, corrected reason token) until a separate acr-side change wires a live adapter in; (4) the two D49 reason tokens are read from the synced wire schema, not hand-typed, so they cannot silently diverge from the engine's own disclosure; (5)_GATE_OUTCOME_PASSED/_SEMANTIC_STATE_FORMAT_VERSIONare hand-mirrored acr internal constants (never on the wire, so no schema exists to read them from) — each cites its acr source file in a comment, the same mirroring discipline this file's own_SERVED_HTTP_STATUSESalready uses.RISK-NOTES
corpus/scoring machinery in ask-dev. No acr code, no wire contract, no corpus rowexpectvalue, and no production consumer of the corpus scorer is touched. The newpersisted_semantic_stateparameter is optional and defaults to the pre-existing conservative behavior (unscored) everywhere it is omitted.any_ofserve branch that used to reach the old ceiling reason now reports one of D49's two closed tokens (semantic_state_absent/semantic_state_unreadable) instead of the oldfamily_confirmation_unavailable— sameunscoredrank, different reason string, for EVERY caller of this ticket'sscore/score_branch/build_verdict, whether or not it passes apersisted_semantic_stateadapter (an omitted adapter reads assemantic_state_absent, the same token an adapter that found no row would produce). A report or dashboard that greps the old reason string by name needs updating the moment this PR lands, independent of whether the trial-store adapter is wired in anywhere yet. Grepped both repos for the old string as a live consumer, not just a docs mention: none found outside this PR's own docs and this module's unrelatedaudit_window_exchangeinternalreasonfield (a different value, on a different key, unaffected by this change).POLICY_VERSIONbump:any_of-fail-closed-v2-persisted-family-link. Any consumer that compares scorer output across versions without checkingpolicy_versionrisks reading a policy change as a correctness change; the field already exists on every published record for exactly this reason.score/score_branchinternal tuple arity changed (eachany_ofbranch result is now a 3-tuple(verdict, reason, detail), was 2-tuple) — verified by grep that no caller anywhere in either repo destructures this shape directly:score_branchis called only fromscore()in this same file, andscore()itself is called only frombuild_verdict()in this same file plus this file's own tests (already updated to the new shape); acr's runner calls onlybuild_verdict(), which publishes branch results as dicts, never raw tuples, so this internal shape is not part of any cross-file or cross-repo contract.format_version/family/validation.gate_outcomethe caller's adapter returns; it never opens a connection itself and performs no authentication of the adapter's source. A companion, already-reviewed acr-side PR is what wires a real Postgres-backed adapter into acr's runner and is where that trust boundary is actually established — this PR alone ships no reachable production caller of the adapter parameter (acr's ownsemantic_verdict_bridge.pyis untouched here; see that companion PR for the wiring and its own forward-compatibility guard against an ask-dev pin that predates this parameter)._GATE_OUTCOME_PASSED("passed") and_SEMANTIC_STATE_FORMAT_VERSION("semantic-state.v1") are copied by hand from acr's Go source (neither is on the wire, so there is no schema to read them from programmatically) — an acr-side rename of either constant needs a matching edit here, same as the existing_SERVED_HTTP_STATUSESmirror this file already carries.expect_schema.py's new reader adds a defensiveRuntimeErrorif the synced schema'ssemantic_reading.reasonenum ever stops being exactly the two known members, so a future wire change fails the corpus test suite loudly rather than silently mis-scoring.