cluster: decode_deterministic 三值 enum + honest gate 抽 helper + CLI 值域 (#118, #120) - #130
cluster: decode_deterministic 三值 enum + honest gate 抽 helper + CLI 值域 (#118, #120)#130kiki830621 wants to merge 2 commits into
Conversation
…onest gate (#118, #120) #118 settled the representation: the field records WHAT WE KNOW, not what the backend is. Bool? overloaded nil to mean both 'legacy row' and 'this backend ignores the flag'. DecodeDeterminism separates them — deterministic-enforced flag consumed, on fallback-enabled flag consumed, off flag-not-consumed backend ignores the flag, and this makes NO claim about whether its decode is actually reproducible Legacy rows stay on field-absence, which is no longer overloaded. An unknown wire value throws DecodingError.dataCorrupted (loud), while absent and explicit null both decode to nil. #120 item 1: the honest gate moves out of the benchmark loop into DecodeDeterminism.forBackend(_:flagRequested:), a pure non-optional function next to flagConsumingBackends. It lives on the enum because the rule IS the meaning of the cases — adding a flag-consuming backend is now one edit in the file that documents the invariant it can violate. The 'never lie' property is finally unit-testable without running a real backend. #120 item 2: --run-kind is a RunKind ExpressibleByArgument (mirroring the existing HallucinationFilterMode precedent), so a typo fails at the CLI boundary with exit 64 instead of surviving to bench CI. MeasurementRow.runKind stays String? — the type constrains the entry point, not the stored row. Both value sets carry a keep-in-sync note pointing at bench's validate_measurements.py; there is no mechanical link (the #120 Residue). bestASR-bench side landed as c93a70e. Refs #118 Refs #120
…l shape (#130) Seven convergent verify findings, all in-scope by the DA's boundary (this PR may fix text it wrote and text it made false, plus tests in the file it created — not what it merely made visible): - CHANGELOG cited bestASR-bench@e728f1a as the authority for the enum. That commit's validator enforces isinstance(..., bool) — it REJECTS every value the paragraph introduces. Now c93a70e. Two legs caught it independently; shipped text that contradicts itself is the worst kind. - The compat sentence covered only an ABSENT field. It now also states what happens to the boolean this same unreleased entry used to describe: rejected, not coerced, with the evidence that nothing ever wrote one. - #120 was missing from the CHANGELOG entirely, though --run-kind bogus failing at parse is user-visible. Added, including WHY the stored field stays a string. - The #109 paragraph said --decode-deterministic is 'forwarded elsewhere', which now reads as contradicting the #118 paragraph beside it. It is a silent no-op elsewhere — that is the whole reason flag-not-consumed exists. - deterministicEnforced's doc claimed 'the same audio yields the same text'. An over-claim inside a PR about not over-claiming — disabling temperature fallback does not establish byte-identical output across runtime, hardware or model revision. It now says what was observed: the setting was enforced. - 'hyphenated snake-case' is kebab-case. - RunKind's doc claimed to be the Swift side's single source of truth. It is not: the row types stay String?, so a library caller can still store anything and bench CI stays the backstop. The comment now states that scope AND the reason it differs from decode_deterministic — that field's domain is derived from this repo's backend roster, while run_kind is human-typed provenance whose vocabulary is already incomplete (the regression gate benchmarks with no --run-kind at all). Closing it at the row type would trade a loud CI failure for a silently dropped row. Plus two tests: the pre-#118 boolean shape is now locked as REJECTED (a census expires, a test does not — and main still contains the bool-writer today), and SubmissionRow's absent-not-null contract is asserted on the type that actually crosses to the bench repo, not only via MeasurementRow. Forward-compat is now documented on the enum: adding a case is breaking for older readers AND fails invisibly, because both consumers turn the throw into a quiet drop. Mechanism-level fix tracked in #131. Refs #118 Refs #120
The flag-not-consumed row listed 'Apple', which is not a backend in ModelGrid — Apple Speech is still an open proposal (bestASR#121). Name what exists: mlx-audio (silent no-op) and the Fluid family (no such knob). Refs PsychQuant/bestASR#130
Verify Report — PR #130 (#118, #120)Enginemanual fan-out — 3 lens Agents(logic / requirements / regression,model: opus)
AggregatePASS — 0 blocking;7 個 in-scope fix 已於 round-1( 要求覆蓋率8 / 8 FULLY(#118 契約 5 項 + #120 三項;requirements lens 逐項對照 Decision comment)。 這個改動是「刻意破壞相容」——安全性用六種方法查證舊
DA 另加一條: Findings(合併後,Fix 欄 = round-1)
DA 的兩項裁決(採納)Q2 — Q5 — 三個值的命名(使用者拍板):裁定保留,且理由優於 orchestrator 的: Scope Check無超出 #118/#120 範圍。Round-1 的 7 項修復嚴格遵守 DA 的邊界判準: Process Gaps無。Codex leg 正常完成。 Follow-up Findings Filed
誠實的範圍聲明本 PR 交付的是 |
Refs #118
Refs #120
Summary
Cluster PR — #120 item 1 and #118 are the same code: the extracted helper's return type is the enum #118 settled on. Doing them apart would have written the helper twice.
#118 —
decode_deterministicwasBool?, andnilmeant two different things: "legacy row" and "this backend ignores the flag". The newDecodeDeterminismenum records what we know, not what the backend is:deterministic-enforced--decode-deterministic, and it was onfallback-enabledflag-not-consumedLegacy rows stay on field-absence, which is now unambiguous.
flag-not-consumeddeliberately does not say "this backend is deterministic": we know the flag wasn't consumed, we don't know what happens inside Parakeet / mlx-audio / Apple.#120 item 1 — the honest gate was inline in the benchmark loop, so the "never lie" invariant could not be tested without running a real backend. It's now
DecodeDeterminism.forBackend(_:flagRequested:), a pure function besideflagConsumingBackends, unit-tested across whisperkit / whisper.cpp / mlx-audio / all three Fluid backends / an unknown future backend.#120 item 2 —
--run-kindis now aRunKind: ExpressibleByArgument, so a typo dies at the CLI boundary (exit 64) instead of reaching bench CI.#120 item 3 landed on the bench side (
PsychQuant/bestASR-bench@c93a70e): both provenance fields now accept explicitnullas equivalent to absent, and the value sets are hoisted to module constants.Timing
The migration was free only right now: bench had 156 rows, zero carrying either field, and no sweep had run since #111 merged. The next release sweep starts accumulating rows in whatever representation is current — this deliberately-breaking change (old
true/falseare now rejected) had to land before that.Verification
Independently re-verified by the orchestrator, not taken on the implementer's report:
swift buildclean;swift test396 tests / 83 suites, 0 failures (baseline on this branch is 389 — the implementer corrected my brief, which had carried 402 over from the Bundle A branch that adds 13 tests of its own).RUN_KINDS/DECODE_DETERMINISM. This is the contract neither side can check alone.--run-kind bogus→exit=64,Error: The value 'bogus' is invalid … Please provide one of 'release-sweep' and 'adhoc'; both legal values pass parse.run_kind: nullwas rejected before / accepted after; the enum values were rejected before / accepted after; oldtrue/falsenow rejected; 156 existing rows stillOK.Checklist
bestASR-bench@c93a70e)Generated by /idd-all cluster mode. Do NOT add a GitHub close trailer — IDD discipline requires manual /idd-close after merge.