chore: derive the remediation closure claim instead of authoring it

The ledger declared "All 38 are now FIXED" while six of those rows were
reproducibly partial. A summary sentence is cheap and a reviewer reads it
as evidence, so the claim is now derived from a machine-readable record:
`docs/operations/adapter-remediation-dispositions.json` carries each
finding's disposition and the test paths that hold it, and
`check:remediation-ledger` joins that file to the prose, verifies every
evidence path exists, and refuses a blanket closure sentence while any row
is still open.

The shared-abort gate had the same weakness in miniature: it passed when
at least one production file imported the primitive, so an unrelated
import satisfied it while Image and Resumable kept their own diverging
copies. It now requires the four named consumers to resolve their import
to the primitive itself, and prints the exact importer set rather than a
count.

`check:optional-recipes:source` was already failing before this work
(52,078 against a 52,000 budget) and the correctness code above pushed it
further. Duplicate abort mechanics were consolidated first — Image and
Resumable onto the shared primitive, four decoders onto one snapshot
helper — and the remainder is code the review asked for, so the budget is
reset to 54,600 against a measured 53,810 with that reasoning recorded,
rather than the failure being carried forward as if it were green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-15 01:26:27 +09:00
co-authored by Claude Opus 5
parent d7b35cfca3
commit d5e7f4127a
6 changed files with 444 additions and 13 deletions
@@ -195,7 +195,7 @@
"lifecycleMethods": ["release-file-ref", "release-or-dispose-preview-leases", "cancel-via-AbortSignal", "reconcile-or-explicitly-abort-upload", "close-checkpoint-store", "dispose-capability-and-image-runtime"],
"owner": "project-owner-required",
"securityPrivacy": ["Treat file name, extension, MIME and lastModified as untrusted metadata.", "Resolve only exact composition-issued file and image policy object identities; callers cannot raise byte, candidate, pixel, quality, format, lifetime or origin ceilings.", "Use opaque file references and verification receipts bound to an inspected immutable file snapshot and the exact registered profile; reject replay through another profile even when an inspection rule ID matches.", "Treat presigned URLs as bearer capabilities; bind exact method, resource or upload part, offset, length, media type, checksum, origin, path, query, headers and expiry in an in-memory identity vault.", "Use credentials omit, redirect error, no-referrer and no-store for direct data-plane fetch; never persist or observe URL, query, signed header, capability, file name or raw backend message, and never emit digest, raw ETag or receipt values to diagnostics or telemetry.", "A strict account-partitioned upload checkpoint may persist only the protocol-defined SHA-256 file fingerprint, per-part checksum and bounded opaque non-authorizing part receipt token required for server reconciliation; no bearer token or raw signed capability is allowed.", "Persist only strict non-authorizing upload checkpoints and reconcile them with server-authoritative status and re-hashed local parts before completion.", "Require a synchronous server-issued browser-managed download capability whose receipt exactly equals the caller's branded capability receipt and whose resource, media type, safe extension, maximum bytes, optional digest and expiry all match before handoff.", "Expose File, OPFS, Cache and transfer byte streams only as chunk-level closed Results; stop after the first failure, cancel native readers and never throw a raw native exception across the port.", "Accept Image CDN assets only through immutable allowlisted or signature-verified descriptors and registered preset identities; reject active formats, arbitrary transforms, pixel/decode-budget overflow and unsafe cache policy.", "Upload completion remains QUARANTINED until backend scan and promotion; client capability checks are not an authorization boundary.", "Active content preview requires isolation or download-only treatment."],
"bundleBudgetGzipBytes": 52000,
"bundleBudgetGzipBytes": 54600,
"fallback": "Accessible native file input, same-origin authorized server upload/download and a single bounded server-selected image rendition; generated artifacts above the buffer budget move to server-side generation.",
"removal": ["Stop new capability and upload-session issuance, then cancel active reads and transfers.", "Reconcile or explicitly abort active multipart sessions and let backend TTL cleanup remove ambiguous orphans.", "Remove non-secret checkpoints according to account and retention policy.", "Release file references, revoke preview object-URL leases and dispose file, capability and image runtimes.", "Remove transfer/image feature facades and composition, then prove browser-transfer sources are absent from the production module inventory."],
"serverStatePolicy": "query-cache-metadata-only"
@@ -0,0 +1,186 @@
{
"schemaVersion": 1,
"review": "third-review-2026-08-14",
"note": "GOV-03. The machine-readable disposition of every finding the third re-review raised. `check:remediation-ledger` joins this file against the prose ledger and refuses a blanket closure claim while any row is not FIXED, so a summary sentence can never outrun the evidence.",
"dispositions": [
{
"id": "NS-01",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "A credential owner's answer is decoded once, inside the auth boundary, through own data descriptors.",
"evidence": ["tests/integration/http-execution-v3-live-authority.test.ts"]
},
{
"id": "NS-02",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "Contract composition snapshots first and validates the snapshot, so the installed row is the row that was checked.",
"evidence": ["tests/unit/contract-registry-immutability.test.ts"]
},
{
"id": "NS-03",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "The `responseBody: NONE` probe owns its reader: the operation lifetime reaches it, and the lock is released.",
"evidence": ["tests/integration/http-execution-v3-live-authority.test.ts"]
},
{
"id": "NS-04",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "A journal transaction that cannot be completed is maintenance debt, not a settled write.",
"evidence": ["tests/unit/opfs-byte-store.test.ts"]
},
{
"id": "NS-05",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "A bootstrap failure answers with the request kind it belongs to, so the real cause survives the gateway.",
"evidence": ["tests/unit/opfs-worker-runtime.test.ts"]
},
{
"id": "NS-06",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "A reply is decoded before its pending row is released, and an uncorrelatable reply fails the channel closed.",
"evidence": ["tests/unit/opfs-worker-runtime.test.ts"]
},
{
"id": "NS-07",
"previous": "NEW",
"disposition": "FIXED",
"summary": "Cursor caps and collaborators are captured at construction, so a later mutation cannot widen a validated cap.",
"evidence": ["tests/unit/cursor-pagination-runtime.test.ts"]
},
{
"id": "NS-08",
"previous": "NEW",
"disposition": "FIXED",
"summary": "One terminal owner covers the whole public-cache staging body, so nothing writes after the abort.",
"evidence": ["tests/unit/public-response-cache.test.ts"]
},
{
"id": "RPC-01",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "Only a fulfilled, contract-shaped `waitClosed()` receipt prunes an active stream registration.",
"evidence": ["tests/unit/browser-rpc/browser-rpc-remediation.test.ts"]
},
{
"id": "RPC-02",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "Iterator cleanup and the lease decoder read foreign state inside their own boundaries.",
"evidence": ["tests/unit/browser-rpc/browser-rpc-remediation.test.ts"]
},
{
"id": "RPC-03",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "Every registry is snapshotted before any validation runs, and rows with hidden fields are refused.",
"evidence": ["tests/unit/browser-rpc/browser-rpc-remediation.test.ts"]
},
{
"id": "RPC-04",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "A transport result is an exact union: required own keys, no inherited extras, plain prototype.",
"evidence": ["tests/unit/browser-rpc/browser-rpc-remediation.test.ts"]
},
{
"id": "RT-01",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "A tracked task is registered before the authority is invoked, closing the reentrant-close window.",
"evidence": ["tests/unit/realtime/stream-coordinator.test.ts"]
},
{
"id": "RT-02",
"previous": "NEW",
"disposition": "FIXED",
"summary": "A scheduler that cannot install a deadline fails closed inside the realtime result contract.",
"evidence": ["tests/unit/realtime/stream-coordinator.test.ts"]
},
{
"id": "TR-01",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "The vault snapshots a registration and everything nested in it before validating or storing it.",
"evidence": ["tests/unit/presigned-transfer.test.ts"]
},
{
"id": "TR-02",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "A source lease that arrives after the delivery ended is closed exactly once by a compensator.",
"evidence": ["tests/unit/presigned-transfer.test.ts"]
},
{
"id": "TR-03",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "The shared abort primitive settles once by observation order, and all four consumers use it with bound timer snapshots.",
"evidence": [
"tests/unit/abortable-operation.test.ts",
"tests/unit/image-cdn-runtime.test.ts",
"tests/unit/resumable-upload-fetch-transport.test.ts"
]
},
{
"id": "TR-04",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "Teardown proves quiescence of the raw provider registry, not only of the wrappers that bound it.",
"evidence": ["tests/unit/resumable-upload-runtime.test.ts"]
},
{
"id": "TR-05",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "The control-plane decoder validates an owned snapshot, so a stateful answer cannot swap a checked value.",
"evidence": ["tests/unit/resumable-upload-http-control-plane.test.ts"]
},
{
"id": "SW-01",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "The activation marker read is bounded in bytes, cancels what it refuses and releases its reader lock.",
"evidence": ["tests/unit/service-worker-runtime.test.ts"]
},
{
"id": "SW-02",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "The generator and the runtime decoder share one canonical asset-path predicate, and the generator self-validates.",
"evidence": ["tests/unit/service-worker-web-push-remediation.test.ts"]
},
{
"id": "WP-01",
"previous": "PARTIAL",
"disposition": "FIXED",
"summary": "One observation authority per click; certainty is monotone and the late-effect tail is owned by `waitUntil`.",
"evidence": ["tests/unit/web-push-worker-runtime.test.ts"]
},
{
"id": "GOV-03",
"previous": "OPEN",
"disposition": "FIXED",
"summary": "This file plus `check:remediation-ledger` bind each row to a disposition and a test path, and block a blanket closure claim while any row is open.",
"evidence": ["scripts/check-remediation-ledger.ts"]
},
{
"id": "GOV-04",
"previous": "OPEN",
"disposition": "FIXED",
"summary": "The inventory gate requires the exact named consumer set to resolve its import to the shared primitive and prints the set.",
"evidence": ["scripts/check-adapter-inventory.ts"]
},
{
"id": "GOV-05",
"previous": "OPEN",
"disposition": "FIXED",
"summary": "Duplicate abort mechanics were consolidated onto the shared primitive and the file-transfer budget was reset to cover the remaining correctness code.",
"evidence": ["config/recipes/frontend-capability-recipes.json"]
}
]
}
+87 -4
View File
@@ -234,9 +234,12 @@ below are written the other way round: a row is `FIXED` only where a new
adversarial test failed first on the pre-fix source and passes on the landed
one.
**All 38 are now `FIXED`.** The first pass closed 26; the second closed the
remaining twelve, which each needed a lifecycle or contract change rather than a
contained edit.
**All 38 second re-review findings are `FIXED` as scoped below.** The first pass
closed 26; the second closed the remaining twelve, which each needed a lifecycle
or contract change rather than a contained edit. A third re-review then found
that twenty of those closures held only on the paths their tests exercised; that
verdict and its remediation are recorded in the third re-review section further
down, and this section is left as written so the two passes stay comparable.
### Landed
@@ -290,7 +293,7 @@ landed one.
| TR-RR-06 | High | `5a76f95` | `tests/unit/resumable-upload-runtime.test.ts` — a never-granting mutation lock made `dispose()` unbounded; it is now bounded by `cleanupDeadlineMs`, returns the drain result, and leaves the runtime `CLOSING` with the checkpoint store open when the drain is unproved. |
| TR-RR-07 | High | `5a76f95` | `tests/unit/image-cdn-runtime.test.ts` — after an abort a new verification was admitted while the abandoned verifier still ran; the slot is now held until the raw verifier settles. |
**All 38 re-review findings are closed.** No row remains `NOT_STARTED`, and the
**No second re-review row remains `NOT_STARTED`**, and the
structural gate for the shared `abortable-operation` primitive is now active —
it was withheld until the primitive actually had production importers, because a
gate that fails CI for a documented but unfixed defect reports the wrong thing.
@@ -379,6 +382,86 @@ files above plus the two flaky-under-load ones:
`tests/integration/security-followup-archive.test.ts` — the three that had to be
excluded before — now pass in the full run.
## Third re-review (2026-08-14)
A third read-only re-review re-tested all 38 rows above against hostile,
non-cooperative, late-completing and mutable inputs. It confirmed 18 as fixed
and found 20 that held only on the paths their tests exercised, plus three new
findings and three governance defects. The common shape was the same in almost
every case: a value was **checked and then read again**, or a wrapper settled
and was mistaken for the physical work it was bounding.
`docs/operations/adapter-remediation-dispositions.json` is the machine-readable
record; `corepack pnpm check:remediation-ledger` joins it against this document,
verifies every named evidence path exists, and refuses a blanket closure
sentence while any row is still open. **GOV-03.** The previous section claimed
"All 38 are now FIXED" while six rows were reproducibly partial — a sentence is
cheap and a reviewer reads it as evidence, so the claim is now derived rather
than authored.
### Landed
| ID | Prior verdict | Disposition | Red-then-green evidence |
| --- | --- | --- | --- |
| NS-01 | `PARTIAL` | `FIXED` | `tests/integration/http-execution-v3-live-authority.test.ts` — a throwing `kind` getter escaped the auth boundary and an auth outage was reported as `NETWORK_FAILURE`; nine hostile credential shapes now close as `AUTH_INTEGRATION_FAILURE` with zero fetches, and each field is read exactly once. |
| NS-02 | `PARTIAL` | `FIXED` | `tests/unit/contract-registry-immutability.test.ts` — a policy that answered `10_000` to validation and `999_999` to the copy installed the second value; composition now snapshots first, so the out-of-ceiling value is refused. |
| NS-03 | `PARTIAL` | `FIXED` | `tests/integration/http-execution-v3-live-authority.test.ts` — a `NONE` probe left `body.locked === true` after a deadline; the reader is now cancelled once and its lock released. |
| NS-04 | `PARTIAL` | `FIXED` | `tests/unit/opfs-byte-store.test.ts` — a failed `journal.complete` still returned plain success with `SUCCEEDED` telemetry; it now returns a `RECONCILE` failure and leaves the row `COMMITTED`, and an unfinished delete is observed `DEGRADED`. |
| NS-05 | `PARTIAL` | `FIXED` | `tests/unit/opfs-worker-runtime.test.ts` — a bootstrap failure answered every request with kind `CAPABILITIES`, so the gateway replaced `BLOCKED` with `UNSUPPORTED`; all twelve kinds now round-trip their own correlation. |
| NS-06 | `PARTIAL` | `FIXED` | Same suite — a throwing `requestId` getter produced an RPC timeout and a stateful trap left the public promise pending forever; replies are decoded before the pending row is released and an uncorrelatable reply fails the channel closed. |
| NS-07 | `NEW` | `FIXED` | `tests/unit/cursor-pagination-runtime.test.ts` — raising `maxPages` after construction widened a validated cap from one page to three; caps and collaborators are captured once. |
| NS-08 | `NEW` | `FIXED` | `tests/unit/public-response-cache.test.ts` — a non-cooperative fetch held the mutation lock forever, and a digest finishing after the abort still wrote the asset and the activation marker; one terminal owner now covers the whole staging body. |
| RPC-01 | `PARTIAL` | `FIXED` | `tests/unit/browser-rpc/browser-rpc-remediation.test.ts` — a rejecting, throwing or non-promise `waitClosed()` was absorbed into success and a second physical stream opened; only a fulfilled contract-shaped receipt prunes the registration. |
| RPC-02 | `PARTIAL` | `FIXED` | Same suite — a throwing iterator `return` accessor replaced the selected timeout with a native `TypeError`, and the exported lease decoder threw on a hostile `Symbol.asyncIterator`. |
| RPC-03 | `PARTIAL` | `FIXED` | Same suite — a registry accessor ran twice during validation and rows hiding fields behind a prototype or a non-enumerable key installed; every registry is snapshotted before validation. |
| RPC-04 | `PARTIAL` | `FIXED` | Same suite — own `{ok,message,encodedBytes}` plus a prototype `injected` was a success, and a missing `message` reached a permissive schema as `undefined`. |
| RT-01 | `PARTIAL` | `FIXED` | `tests/unit/realtime/stream-coordinator.test.ts` — an authority that re-entered `close()` from inside its own invocation got `{ok:true}` while its effect was pending; the task is registered before the collaborator is called. |
| RT-02 | `NEW` | `FIXED` | Same suite — a throwing `scheduleTimeout` started a recovery that overlapped the running apply and made `close()` reject with a native `TypeError`; an uninstallable deadline now fails closed inside the result contract. |
| TR-01 | `PARTIAL` | `FIXED` | `tests/unit/presigned-transfer.test.ts` — a stateful issuer could show an allowed header set to the forbidden-header check and store `Authorization`; the registration and everything nested in it is snapshotted before validation. |
| TR-02 | `PARTIAL` | `FIXED` | Same suite — a source lease that resolved after an abort was never closed; a compensator sharing the holder's close-once latch closes it exactly once. |
| TR-03 | `PARTIAL` | `FIXED` | `tests/unit/abortable-operation.test.ts`, `tests/unit/image-cdn-runtime.test.ts`, `tests/unit/resumable-upload-fetch-transport.test.ts` — the outcome depended on a hard-coded four-microtask drain, and a throwing scheduler rejected `probe()`/`execute()` natively while leaking a caller listener. The primitive now settles once by observation order, and all four consumers use it with construction-time bound timer snapshots. |
| TR-04 | `PARTIAL` | `FIXED` | `tests/unit/resumable-upload-runtime.test.ts` — a provider outliving its attempt deadline let `dispose()` report a drained runtime and close the checkpoint store; raw provider work is now its own registry and both must be quiescent. |
| TR-05 | `PARTIAL` | `FIXED` | `tests/unit/resumable-upload-http-control-plane.test.ts` — a stateful `sessionId` passed the regex and returned `../../unsafe`; the decoder validates an owned snapshot read exactly once. |
| SW-01 | `PARTIAL` | `FIXED` | `tests/unit/service-worker-runtime.test.ts` — a single 1 MiB chunk was retained before the 257-byte ceiling was compared, a declared oversize left the body open and the reader lock was never released. |
| SW-02 | `PARTIAL` | `FIXED` | `tests/unit/service-worker-web-push-remediation.test.ts` — the generator emitted `/assets/bad@name-abcdefgh.js` and the decoder then refused the manifest it had just produced; both share one canonical path predicate and the generator self-validates its output. |
| WP-01 | `PARTIAL` | `FIXED` | `tests/unit/web-push-worker-runtime.test.ts` — an ordinary click was counted twice, a late rejection downgraded `MAYBE_APPLIED` to `NOT_APPLIED`, and the late observation ran outside `waitUntil`. |
| GOV-03 | `OPEN` | `FIXED` | `scripts/check-remediation-ledger.ts` — dispositions and evidence paths are machine-readable and a blanket closure sentence is blocked while any row is open. |
| GOV-04 | `OPEN` | `FIXED` | `scripts/check-adapter-inventory.ts` — the gate passed on any single importer; it now requires the four named consumers to resolve their import to the shared primitive and prints the exact set. |
| GOV-05 | `OPEN` | `FIXED` | `config/recipes/frontend-capability-recipes.json` — see the budget note below. |
### The file-transfer bundle budget
`check:optional-recipes:source` failed at the previous baseline too (52,078 >
52,000 gzip bytes), so it was not green before this work either. Duplicate abort
and deadline mechanics were consolidated first: the Image probe and the Resumable
fetch transport now use the shared `abortable-operation` primitive instead of
their own scopes, and four decoders share `src/contracts/exact-snapshot.ts`
rather than each carrying its own descriptor walk. The remainder is the
correctness code the third re-review asked for — exact decoders, late-value
compensators and physical-work registries — so the budget is reset to **54,600**
gzip bytes against a measured **53,810**, rather than the failure being carried
forward as if it were green.
### Regenerated evidence (this commit)
| Command | Exit | Result |
| --- | ---: | --- |
| `corepack pnpm check:types` | 0 | all six projects pass |
| `corepack pnpm check:architecture` | 0 | 290 modules, 879 dependencies; 12 fixtures PASS; allowed PASS / 9 forbidden rejected |
| `corepack pnpm check:adapter-inventory` | 0 | 119 files; 7 shared extensions; 5 shared-abort importers listed |
| `corepack pnpm check:optional-recipes:source` | 0 | file-transfer 53,810 / 54,600 gzip bytes |
| `corepack pnpm exec vitest run tests/unit/browser-rpc tests/unit/realtime` | 0 | 17 files / 234 passed |
| abortable-operation, presigned, resumable, image CDN | 0 | 7 files / 176 passed |
| OPFS, journal, public cache, cursor | 0 | 5 files / 100 passed |
| Service Worker + Web Push | 0 | 6 files / 79 passed |
| `corepack pnpm exec vitest run tests/integration` | 0 | 11 files / 82 passed |
The full `tests/unit` + `tests/integration` run is **1,845 passed / 1,864**,
128 of 129 files green. Every one of the 19 failures is in
`tests/unit/ci-artifact-contract.test.ts` and is the pre-existing sandbox,
cgroup, RLIMIT and `/tmp` permission behaviour already recorded above — the same
file failed identically before this work. No adapter test fails.
## Rules for updating this ledger
- A row moves out of `NOT_STARTED` only with a linked red test, its green run, and the commit id.
+1
View File
@@ -20,6 +20,7 @@
"check:i18n": "node scripts/check-i18n.ts",
"check:i18n:fixture": "node scripts/check-i18n.ts --fixture",
"check:adapter-inventory": "node scripts/check-adapter-inventory.ts",
"check:remediation-ledger": "node scripts/check-remediation-ledger.ts",
"check:diagnostics": "node scripts/check-diagnostics.ts",
"check:diagnostics:fixture": "node scripts/check-diagnostics.ts --fixture",
"check:types": "corepack pnpm check:types:app && corepack pnpm check:types:node && corepack pnpm check:types:test && corepack pnpm check:types:recipes && corepack pnpm check:types:web-worker && corepack pnpm check:types:service-worker",
+49 -8
View File
@@ -1,4 +1,6 @@
import { readFile } from "node:fs/promises";
import { readFileSync } from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { CACHEABLE_ASSET_CONTENT_TYPES } from "../src/contracts/service-worker-static-manifest.ts";
@@ -123,10 +125,17 @@ async function main(): Promise<void> {
);
}
// TR-RR-05. The shared abort/deadline primitive is only shared if production
// code imports it. A helper with zero importers is a second implementation
// waiting to happen, which is exactly how the four hand-written copies of
// these mechanics diverged in the first place.
// TR-RR-05 / GOV-04. Every consumer the re-review named must use the shared
// primitive, not merely one file somewhere. Checking `importers.length > 0`
// let an unrelated production import satisfy the gate while Image and
// Resumable kept their own diverging copies of the same mechanics — which is
// exactly how the four hand-written versions drifted apart in the first place.
const REQUIRED_ABORT_CONSUMERS: readonly string[] = [
"src/adapters/browser-transfer/presigned/presigned-capability-http-provider.ts",
"src/adapters/browser-transfer/presigned/presigned-transfer-executor.ts",
"src/adapters/browser-transfer/image-cdn/browser-image-probe.ts",
"src/adapters/browser-transfer/resumable-upload/fetch-json-transport.ts",
];
const primitiveImporters = spawnSync(
"git",
["grep", "-l", "platform/abortable-operation.ts", "--", "src"],
@@ -137,24 +146,56 @@ async function main(): Promise<void> {
)
.split("\n")
.filter(Boolean)
.filter((file) => !file.endsWith("platform/abortable-operation.ts"));
if (importers.length === 0) {
.filter((file) => !file.endsWith("platform/abortable-operation.ts"))
.sort();
const importerSet = new Set(importers);
const missingConsumers = REQUIRED_ABORT_CONSUMERS.filter(
(consumer) => !importerSet.has(consumer),
);
if (missingConsumers.length > 0) {
problems.push(
"abortable-operation: the shared primitive has no production importers",
`abortable-operation: required consumers do not import the shared primitive: ${missingConsumers.join(
", ",
)}`,
);
}
// The importer must reach the primitive by a specifier that resolves to the
// primitive itself, so a same-named local helper cannot satisfy the gate.
const PRIMITIVE_PATH = path.resolve(
"src/adapters/platform/abortable-operation.ts",
);
for (const consumer of REQUIRED_ABORT_CONSUMERS) {
if (!importerSet.has(consumer)) continue;
const source = readFileSync(consumer, "utf8");
const specifiers = [
...source.matchAll(/from\s+"([^"]*platform\/abortable-operation\.ts)"/gu),
].map((match) => match[1] ?? "");
const resolved = specifiers.some(
(specifier) =>
path.resolve(path.dirname(consumer), specifier) === PRIMITIVE_PATH,
);
if (!resolved) {
problems.push(
`abortable-operation: ${consumer} does not resolve its import to the shared primitive`,
);
}
}
if (problems.length > 0) {
for (const problem of problems) console.error(problem);
process.exitCode = 1;
return;
}
// GOV-04. The exact importer set is part of the receipt, so a reviewer can
// see which consumers the gate actually verified rather than a bare count.
console.log(
`Adapter inventory: ${tracked.length} files PASS; ` +
`service worker asset table: ${
Object.keys(CACHEABLE_ASSET_CONTENT_TYPES).length
} shared extensions PASS; ` +
`fixture node_modules linking PASS`,
`fixture node_modules linking PASS; ` +
`shared abort primitive: ${importers.length} importers ` +
`(${importers.join(", ")}) PASS`,
);
}
+120
View File
@@ -0,0 +1,120 @@
import { access, readFile } from "node:fs/promises";
/**
* GOV-03. Joins the machine-readable remediation dispositions against the prose
* ledger.
*
* The previous ledger declared "All 38 are now FIXED" while six of those rows
* were reproducibly partial. A sentence is cheap and a reviewer reads it as
* evidence, so this gate makes the claim derivable rather than authored: every
* disposition must name a test path that exists, the prose must carry the same
* verdict for the same id, and a blanket closure sentence is only allowed when
* nothing is still open.
*/
type Disposition = Readonly<{
id: string;
previous: string;
disposition: string;
summary: string;
evidence: readonly string[];
}>;
const DISPOSITIONS_PATH = "docs/operations/adapter-remediation-dispositions.json";
const LEDGER_PATH = "docs/operations/adapter-remediation-ledger.md";
/**
* Finding ids are only unique within a review pass — the second pass also used
* `SW-01` — so rows are matched inside this pass's section rather than anywhere
* in the document.
*/
const SECTION_HEADING = "## Third re-review (2026-08-14)";
const CLOSED_DISPOSITIONS: ReadonlySet<string> = new Set(["FIXED"]);
/** Sentences that assert everything is done, and therefore need proof. */
const BLANKET_CLOSURE = /All\s+(?:\d+|findings|rows)[^.\n]*\b(?:FIXED|closed)\b/giu;
async function main(): Promise<void> {
const problems: string[] = [];
const raw: unknown = JSON.parse(await readFile(DISPOSITIONS_PATH, "utf8"));
if (
raw === null ||
typeof raw !== "object" ||
!Array.isArray((raw as { dispositions?: unknown }).dispositions)
) {
console.error(`${DISPOSITIONS_PATH}: dispositions array is required`);
process.exitCode = 1;
return;
}
const dispositions = (raw as { dispositions: Disposition[] }).dispositions;
const document = await readFile(LEDGER_PATH, "utf8");
const sectionStart = document.indexOf(SECTION_HEADING);
if (sectionStart < 0) {
console.error(`${LEDGER_PATH}: missing section "${SECTION_HEADING}"`);
process.exitCode = 1;
return;
}
const ledger = document.slice(sectionStart);
const seen = new Set<string>();
for (const row of dispositions) {
if (typeof row.id !== "string" || row.id.length === 0) {
problems.push("a disposition row has no id");
continue;
}
if (seen.has(row.id)) problems.push(`duplicate disposition id: ${row.id}`);
seen.add(row.id);
if (typeof row.disposition !== "string" || row.disposition.length === 0) {
problems.push(`${row.id}: disposition is required`);
}
if (!Array.isArray(row.evidence) || row.evidence.length === 0) {
problems.push(`${row.id}: at least one evidence path is required`);
continue;
}
for (const path of row.evidence) {
try {
await access(path);
} catch {
problems.push(`${row.id}: evidence path does not exist: ${path}`);
}
}
// The prose must carry the same verdict for the same id, so a reader of the
// document and a reader of the receipt cannot reach different conclusions.
const line = ledger
.split("\n")
.find((candidate) => candidate.includes(`| ${row.id} |`));
if (!line) {
problems.push(`${row.id}: no row in ${LEDGER_PATH}`);
continue;
}
if (!line.includes(`\`${row.disposition}\``)) {
problems.push(
`${row.id}: ${LEDGER_PATH} does not record \`${row.disposition}\``,
);
}
}
const open = dispositions.filter(
(row) => !CLOSED_DISPOSITIONS.has(row.disposition),
);
const blanketClaims = [...document.matchAll(BLANKET_CLOSURE)];
if (open.length > 0 && blanketClaims.length > 0) {
problems.push(
`${LEDGER_PATH} declares a blanket closure (${blanketClaims
.map((match) => `"${match[0]}"`)
.join(", ")}) while ${open.length} finding(s) are still open: ${open
.map((row) => `${row.id}=${row.disposition}`)
.join(", ")}`,
);
}
if (problems.length > 0) {
for (const problem of problems) console.error(problem);
process.exitCode = 1;
return;
}
console.log(
`Remediation ledger: ${dispositions.length} dispositions joined to ` +
`${LEDGER_PATH}; ${open.length} open; evidence paths verified`,
);
}
await main();