A forbidden input that only a document forbids is not a firewall - #814
Merged
Conversation
PR 2 of CDEB-Fresh v3 (PRD §23). The study's independence rests on eighteen roles that each must not see certain things: GOLD-A must not see the record payload it is reconstructing, PATCH-A must not see the arm it is judging, STAT-B must not see STAT-A's code. §3.4 states those inputs; §3.6 requires each prompt be versioned and hashed. Stated is not enforced, so this adds the enforcement. `roles.ts` verifies every prompt's SHA-256 against the bytes on disk and refuses the manifest by name when one drifted. An edited prompt with a stale hash is a hard failure rather than a warning: a role whose prompt changed silently is a role whose forbidden inputs are unknown. `forbiddenInputViolations` answers what a caller is about to hand a role, so later PRs gate on it instead of on care. §3.5's model-family requirement and §3.8's no-self-approval rule differ in kind and are modelled that way. Reusing a session for a pair is invalid. Sharing a model family is a DOWNGRADE: the study still runs, the label becomes single-family-internally-replicated, and README headline eligibility is HOLD. Collapsing those two into one boolean would have made an honest downgrade look like a failure and tempted the opposite. The lifecycle is thirteen forward-only states. `appendTransition` refuses a backward move, refuses an artifact that fails the schema, and refuses any row whose `study_id` belongs to another study, naming both ids -- §5.2 forbids reusing old result rows, and a v1.3 row must be unreadable as a v3 row rather than merely unwelcome. Four schemas join the two from PR 1: the §4.2 transition artifact, §21.2's run row, §21.3's patch audit row, and gold. Where §8 named a field without fixing its labels, the narrower enum was chosen so a later author cannot introduce an unreviewed semantic category by writing one. Record-Id: r-v3rolegov Provenance: authored Certainty: firm Blast: module Undo: easy Ruled-out: keeping the role manifest as documentation and checking it in review | the manifest lists what each role must not see, and a list nobody executes is satisfied by any behaviour at all Ruled-out: treating a same-model-family pair as an error | §3.5 permits the study to run single-family and requires it be labelled and held; refusing outright would lose a real study, and passing silently would publish an overclaim Ruled-out: comparing the role list in the test against a second list written beside it | a copy agrees with its original and proves nothing, so completeness is read from the PRD table Limit: the firewall checks what a caller declares it is offering a role. A caller that mislabels an input passes, so this bounds accident and not deception, and the sealed-store split is what bounds the latter Limit: prompt hashes bind the prompt text, not the context a runner actually assembles; PR 5 is where the assembled context becomes observable Verified: 48 cases across three v3 suites, tsc clean. Four negative controls, each observed failing and then restored -- a backward transition accepted by canTransition, a required field removed from run-row.schema.json, one character appended to gold-a.md, and one forbidden input removed from GOLD-A's manifest entry CommitLore-Version: 2.0.0
CommitLore — record lintTrailers: clean — 2 commits in Active constraints for the paths this PR touchesLimits (14)
Ruled out (29)
Warnings (8)
Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR. |
…ess for a template
Three corrections to PR 2, each found by running what CI runs rather than what I ran.
The bench typecheck. `bench/tsconfig.json` sets `verbatimModuleSyntax`, under which
`ajv-formats`' default import resolves to an uncallable module namespace. The root
typecheck was clean and the bench one was not; CI runs both and I ran one. The plugin is
gone from `bench/`: the only format the transition schema uses is `date-time`, and it is
now registered as an explicit rule this repository can read. A static assertion fails on
the machine that writes such an import instead of ten minutes later.
The gold schema required six fields §8.8 does not print -- study_id, record_id, the two
annotator ids, the adjudicated resolution and the source-packet digest. They came from my
instruction, derived from §8.2 and §8.4, and they made the PRD's own example invalid under
the schema meant to serve it. The PRD is the sole authority for this implementation, so
its printed shape is the required shape; those six are typed and optional now.
Then the guard for that. I asked for §8.8's example to validate, and §8.8 is a template
whose values are placeholders -- "40-hex", "64-hex", "r-...". The only way to satisfy that
is to widen the patterns, which is what happened: `^(?:[0-9a-f]{40}|40-hex)$` would have
let a real gold file record its snapshot as the word "40-hex". The instruction was wrong,
not the constraint. The guard now compares the schema's required set against the field
names §8.8 prints and reports the difference in both directions, which catches a schema
stricter than its authority and one looser than it. Values are exercised where values
belong: against a real instance.
The lesson is the one this PR keeps re-teaching. A check that names the wrong target does
not merely fail to help -- it pushes the implementation toward the wrong property, and the
implementation obliges.
Record-Id: r-v3schemaauth
Provenance: authored
Certainty: firm
Blast: module
Undo: easy
Ruled-out: relaxing bench/tsconfig.json so the plugin import compiles | that alters a build contract to accommodate one import, and the module settings are not the thing that is wrong
Ruled-out: casting the plugin to a callable | it hides the interop mismatch rather than removing it, and the cast would outlive whoever understood it
Ruled-out: keeping the widened patterns and noting the placeholder exception in a comment | a comment does not stop a validator from accepting the word "40-hex" where a commit id belongs
Limit: the shape guard binds the field set, not the field semantics; §8.8 could rename a field's meaning while keeping its name and nothing here would notice
Verified: 51 cases across three v3 suites, and both typechecks -- root and bench -- clean. Three negative controls, each observed failing and then restored: an ajv-formats import re-added under bench/, a field added to the schema's required set that §8.8 does not print, and the real gold instance's snapshot_sha set to the word "40-hex", which the restored pattern refuses. bench/verify.mjs still validates 1902 rows across 17 files
CommitLore-Version: 2.0.0
MongLong0214
added a commit
that referenced
this pull request
Aug 21, 2026
…hing was
A corrective review measured the study's state against its own claims and found the
state ahead of the evidence. `transitions.jsonl` recorded
DRAFT -> LITERATURE_LOCKED actor UNKNOWN
checks: ["STATUS.json records phase literature-lock",
"git commit 2746dbd created STATUS.json"]
on a tree where source-lock held 0 sources, the evidence matrix 0 claims, and the audits
directory was empty. The checks are circular: both cite the artifact that asserts the
lock. Evidence for a lock cannot be the file claiming it.
That defect is worse than one wrong row. A LITERATURE_LOCKED that passes on empty
artifacts makes CORPUS_QUALIFIED pass on empty artifacts too, and the state machine stops
being a gate and becomes a label.
The row is preserved rather than corrected. Editing a ledger to fix a ledger produces
exactly the artifact an auditor cannot distinguish from the original mistake, so
`cdeb-fresh-v3` moves to a new terminal INVALIDATED state, the deviation carries the
offending line's bytes and its sha256 beside the counts that contradict it, and
`cdeb-fresh-v3r1` starts at DRAFT with an empty selection and a null seed.
Entering LITERATURE_LOCKED now measures what it asserts: sources above zero, claims above
zero, LIT-A/LIT-B/LIT-C artifacts present, unresolved claims zero, and a named actor. A
check whose text contains its own destination state is refused as circular, which is the
specific shape that got through.
Three provenance leaks close with it. Candidates were written as `benchmark: "cdeb-v1"`
with no study_id, so a legacy row and a fresh one were indistinguishable by contract and
PR #814's cross-study refusal never reached them; the v3 contract requires study_id and
source_snapshot_sha and refuses the legacy field outright. The census passed no exclusion
list, leaving the enumerator's benchmark-authored option unused; a fifteen-row exclusion
index now gates it, each row carrying the path or commit that evidences it rather than an
inference from how a record id looks. And the census read whatever `dist` the working tree
happened to hold while the manifest claimed a release, so the release digest is recorded
and checked.
One guard here was written too wide and had to be narrowed. Asserting that no personal
path appears anywhere under `bench/` made four historical runner logs lose the path a past
run actually wrote to. Those logs are evidence of what happened; a guard that forces
recorded history to be rewritten is worse than the path it removes. They are restored, and
the assertion now scans code and active-study artifacts with `bench/results/` and
`bench/cdeb/archive/` named as explicit exclusions, so widening it later is a visible edit.
No measured data existed while any of this was true. Selection is empty, rows and pilot
are empty, the seed is null and measured runs remain disallowed, which is the only reason
the study is recoverable rather than lost.
Record-Id: r-v3corrective
Provenance: authored
Certainty: firm
Blast: system
Undo: costly
Ruled-out: editing the false transition in place | the edit is indistinguishable from the mistake it repairs, and an auditor reading the ledger afterwards cannot tell which they are looking at
Ruled-out: keeping one study instance and rewinding its state | the lifecycle is forward-only by design, and a rewind is the same lie as the premature advance with the sign flipped
Ruled-out: renaming the benchmark field and keeping the v1 contract | the field is not the problem; a contract that cannot distinguish a legacy candidate from a fresh one is
Ruled-out: inferring benchmark-authored status from record id spelling | a naming convention is not evidence of intent, and the review names this shortcut explicitly
Ruled-out: rewriting the historical runner logs to satisfy the path guard | those bytes record where a run actually wrote, and a guard is not a licence to change what happened
Limit: the exclusion index is only as complete as the evidence found for it; one row is recorded as ambiguous-pending-adjudication rather than asserted either way, and an unknown old artifact that left no trace in this repository cannot be excluded by it
Limit: the literature lock is still not done. This makes the gate real; it does not populate the sources, and the active study stays at DRAFT until LIT-A/B/C actually run over originals
Limit: the release binding checks the digest this repository can compute today; a release whose shipped artifact cannot be reproduced locally is out of its reach
Verified: 3496 cases pass across the full suite with 13 skipped, both typechecks clean, and the four v3 suites green at 72. Negative controls observed and restored: a source added while claims stayed empty is still refused, a circular check passes only with the guard disabled, a legacy cdeb-v1 row is refused by identity, a deleted exclusion index stops the census, a personal path in census.ts fails the guard while the same string inside bench/results does not, and git status shows bench/results unchanged
CommitLore-Version: 2.0.0
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.
PR 2 of CDEB-Fresh v3 (PRD §23): role governance, information firewalls, role prompt hashing, study/gold/task/run/patch schemas, the state machine and transition ledger, and cross-study ingestion refusal.
What is enforced, not just stated
§3.4 gives eighteen roles their allowed and forbidden inputs; §3.6 requires each prompt be versioned and hashed. This PR makes both executable.
roles.tsverifies every prompt's SHA-256 against the bytes on disk and refuses the manifest by name when one drifted. A prompt edited without updating its hash is a hard failure — a role whose prompt changed silently is a role whose forbidden inputs are unknown.forbiddenInputViolations(roleId, offered)answers what a caller is about to hand a role, so later PRs gate on it instead of on care. Offering GOLD-A the record payload, or PATCH-A the arm label, is reported.Two constraints that differ in kind
§3.8 (no self-approval) and §3.5 (model-family diversity) are modelled separately rather than as one boolean:
§3.5 permits a single-family study to run provided it is labelled and held. Refusing outright would lose a real study; passing silently would publish an overclaim.
Lifecycle and cross-study refusal
Thirteen forward-only states (§4.1).
appendTransitionrefuses a backward move, refuses an artifact failing the schema, and refuses any row whosestudy_idbelongs to another study, naming both ids. §5.2 forbids reusing old result rows — a v1.3 row must be unreadable as a v3 row, not merely unwelcome.Schemas
Four join the two from PR 1: the §4.2 transition artifact, §21.2's run row, §21.3's patch audit row, and gold. Where §8 named a field without fixing its labels, the narrower enum was chosen so a later author cannot introduce an unreviewed semantic category by writing one.
Verification
48 cases across three v3 suites,
tsc --noEmitclean. Four negative controls, each observed failing and then restored:Limits, in the commit
The firewall checks what a caller declares it is offering. A caller that mislabels an input passes, so this bounds accident and not deception; the sealed-store split bounds the latter. Prompt hashes bind the prompt text, not the context a runner assembles — PR 5 is where the assembled context becomes observable.