Make CONTRACT-001 content-digest honour the shared-drift allowlist its sibling honours
What this fixes
CONTRACT-001 has two sub-checks. They run over the same operation set, and they disagreed about which divergences are accepted.
operation-parity reads compare()'s findings, which are post-allowlist. content-digest re-derived its own list from the raw shared keys and subtracted nothing:
const sharedKeys = [...repoOps.keys()].filter((k) => liveOps.has(k)).sort();
for (const key of sharedKeys) { /* … digest every one … */ }
That is not a stricter check. It is an incoherent one, and the incoherence is load-bearing: operation-parity deliberately filters shared-drift out of its own verdict, with a comment saying that condition is "already reported, more precisely, by content-digest below." So the allowlist's entire shared-drift direction was suppressing a finding that nothing then consumed — nine documented, owner-attributed, predicate-guarded exemptions were dead letters, and the criterion they were written for failed on them anyway. Either the allowlist means something to this criterion or the shared-drift direction should not exist; it cannot half-mean something.
Confirming the asymmetry before changing anything
Measured against origin/main and a snapshot of the published contract fetched 2026-09-11:
- The allowlist holds 11 entries: 9
shared-drift+ 2unpublished-repo. Only the 9 are candidates for subtraction here. (The 11 inoperation-parity's evidence line is the total, not the shared-drift count.) - All 9 still satisfy their lapsing predicate, and all 9 differ under the digest — so all 9 were being both exempted and failed simultaneously.
What changed
The digest now excludes exactly the operations compare() still exempts — nothing more.
Granularity is per-operation: an exempted operation contributes neither its own object nor its reachable $ref content. That is deliberate and it is measured, not assumed. Six of the nine also diverge in referenced component schemas, precisely because of the divergence their entry documents (a gateway-native or draft-placeholder operation reaches different components than the hand-written one), so a refs-only carve-out would exempt the stated cause and still fail on the consequence that flows from it.
The residual risk is stated plainly in the file header rather than hidden: while an exemption stands, a change to a component only that operation reaches is not seen. That is intrinsic to granting the exemption, not introduced here, and it is bounded by machinery that already exists — every entry carries a lapsing predicate, a lapsed entry drops straight back into findings and therefore back into this digest, and compare() separately surfaces entries matching nothing.
The exempted count is now reported in the evidence line on pass and fail both, so a growing exemption set is visible in the receipt itself.
opts.allowlistPath is an in-process test seam only, deliberately not env-readable — the CLI passes no opts, so a real run can only ever read the committed allowlist at its fixed path.
Proof — local runs only
CI is unmeasured org-wide right now (an account-level billing lock removed the required checks); nothing below comes from CI, and this PR is opened as a draft so auto-merge cannot arm.
| measurement | before | after |
|---|---|---|
new tests in contract-001-check.test.mjs | 0 / 6 pass | 6 / 6 pass |
npm run test:ga | 35 / 35 | 41 / 41 |
node --test ".github/scripts/**/*.test.mjs" | 93 / 93 | 93 / 93 |
differing shared ops, origin/main spec | 56 of 255 | 47 (9 excluded) |
| differing shared ops, PR #101's spec | 20 of 255 | 11 (9 excluded) |
The pass-after test fails on origin/main for the right reason — content-digest.ok is false !== true, not a missing option.
content-digest still FAILS after this change, and that is the correct outcome. This PR removes a defect in the criterion; it does not clear the criterion. Eleven genuine content differences remain.
Both directions are pinned
Subtracting an exemption from a digest is exactly the shape of change that can quietly become a way to switch a criterion off, so showing that an allowlisted difference stops failing is not enough. The new tests also hold that:
- a non-allowlisted difference still fails, while a sibling exemption stands;
- a non-allowlisted
$ref-only difference still fails (the reachable-ref fold survives the subtraction); - an entry whose lapsing predicate no longer matches is not subtracted;
- an entry in any other direction is not subtracted — the subtraction is keyed on
shared-drift, not on path+method.
An exemption may only ever remove an operation from the comparison. It may never turn a real mismatch into a pass.
The eleven real differences — reported, not allowlisted
No allowlist entry was added. The allowlist is unchanged at 11. These are itemised in CHANGELOG.md for separate per-operation judgement:
- Six are the 3.0-vs-3.1
nullabledivergence (POST /compose,POST /compose/run,GET /compose/proposals/{proposalId}, plus prose-only diffs onPOST /render,POST /captions,POST /transcribe).CHANGELOG.mdalready records this and states it "does not affect the …shared-driftfinding" — that is true of the operation-level comparator only.content-digestresolves$refcontent and does see it. That sentence needs correcting; this PR corrects it. - Three reach
Error/ErrorBodyschemas the published contract does not define at all (GETandPOST /videos/{videoId}/chapters,POST /videos/{videoId}/chapters/detect). A missing error envelope is a real spec-vs-service gap. - Two are repo-side validation constraints absent live —
SearchIndexRequest/SearchIndexDoc.id.pattern, andBraidPublishRequest.sources.minItems: 2with itsanyOfrequired-one-of. These are the interesting ones: the repo promises a constraint the gateway may not enforce.
None is an allowlist candidate on current evidence.
Relationship to #101
Independent, no file overlap in the fix itself. #101 edits openapi.yaml + contract-drift.json; this edits scripts/ga/*. Both add to CHANGELOG.md under [Unreleased] — expect a trivial textual conflict there and nothing more. #101's own body named this defect and left it for a reviewed change; this is that change.
🤖 Generated with Claude Code
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.
Summary by Sourcery
Align CONTRACT-001 content-digest with operation-parity by applying only active shared-drift exemptions while preserving detection of all other content mismatches.
Bug Fixes:
- Make CONTRACT-001's content-digest check honor active shared-drift allowlist exemptions consistently with operation-parity.
- Ensure expired or differently directed allowlist entries do not suppress digest mismatches.
Enhancements:
- Expose compared, shared, and excluded operation counts in content-digest evidence.
- Add an in-process allowlist path for hermetic testing without changing the CLI's committed allowlist behavior.
Documentation:
- Document the allowlist behavior, residual risk, and the remaining genuine content differences in the changelog.
Tests:
- Add coverage confirming allowlisted operations are excluded while non-allowlisted operation and reachable-reference differences still fail.