From 993e8ae0df7d3f3ae97e8bf4c9685288730b1347 Mon Sep 17 00:00:00 2001 From: iperev Date: Thu, 30 Jul 2026 12:48:21 +0200 Subject: [PATCH 1/2] fix: harden proof and package boundaries --- .../audit-remediation-design.md | 3182 ------------- docs/implementation/audit-remediation-plan.md | 4077 ----------------- docs/release-process.md | 17 +- internal/app/cli_abi_test.go | 2 +- internal/app/cli_contract_test.go | 57 +- internal/app/command_contract_generated.go | 26 +- internal/app/command_coverage_source.go | 37 +- internal/command/publicapi/public_api_test.go | 4 +- .../readinesscloseout/readinesscloseout.go | 6 +- .../readinesscloseout_test.go | 10 + .../repo_profile_admission.go | 15 +- .../repo_profile_admission_test.go | 22 + .../requirementbrowser/http_handler.go | 64 +- .../requirementbrowser/http_handler_test.go | 50 + internal/command/requirementbrowser/server.go | 5 +- .../command/requirementbrowser/server_test.go | 4 +- .../command/requirementbrowser/workspace.go | 4 +- .../requirementcontext_test.go | 23 + internal/command/requirementcontext/slice.go | 10 +- .../stackpreset/preset_ids_generated.go | 2 +- internal/kernel/admission/json.go | 83 +- internal/kernel/admission/json_test.go | 33 + internal/kernel/admit/fields.go | 28 +- internal/kernel/admit/fields_test.go | 12 +- .../compactproofcontract.go | 14 +- internal/kernel/gotestsource/oracle.go | 47 + internal/kernel/gotestsource/oracle_test.go | 34 + internal/kernel/stablejson/stablejson.go | 16 +- internal/kernel/stablejson/stablejson_test.go | 30 + internal/tools/browserproofverify/main.go | 6 +- .../tools/browserproofverify/main_test.go | 6 +- internal/tools/coveragemetrics/main.go | 37 +- internal/tools/coveragemetrics/main_test.go | 23 + internal/tools/mermaidcheck/main.go | 39 +- internal/tools/mermaidcheck/main_test.go | 43 + internal/tools/packageverify/main_test.go | 2 +- internal/tools/pypiregistry/main.go | 35 +- internal/tools/pypiregistry/main_test.go | 15 + .../tools/pythonpackage/continuation_test.go | 4 +- internal/tools/pythonpackage/metadata_test.go | 12 + internal/tools/pythonpackage/verify.go | 36 + package.json | 4 +- proofkit/cli-contract.v2.json | 147 +- proofkit/witness-plan.json | 2 + scripts/browser-runtime-proof-inputs.v1.json | 2 + scripts/stable-json.mjs | 24 + scripts/stable-json.test.mjs | 16 + scripts/workflow_source_oracles_test.go | 50 + scripts/write-browser-proof.mjs | 3 +- tests/browser/workspace.spec.mjs | 46 +- 50 files changed, 928 insertions(+), 7538 deletions(-) delete mode 100644 docs/implementation/audit-remediation-design.md delete mode 100644 docs/implementation/audit-remediation-plan.md create mode 100644 internal/command/requirementbrowser/http_handler_test.go create mode 100644 internal/kernel/gotestsource/oracle.go create mode 100644 internal/kernel/gotestsource/oracle_test.go create mode 100644 scripts/stable-json.mjs create mode 100644 scripts/stable-json.test.mjs diff --git a/docs/implementation/audit-remediation-design.md b/docs/implementation/audit-remediation-design.md deleted file mode 100644 index c031a16..0000000 --- a/docs/implementation/audit-remediation-design.md +++ /dev/null @@ -1,3182 +0,0 @@ -# Audit Remediation Design - -Status: C-124 implementation candidate; C-01 through C-124 -corrections are present; provider validation is invalidated by the C-124 -semantic delta; unaffected prior validations remain historical evidence. - -Owner: `proofkit`. - -Target baseline: `3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64`. - -Review history: - -- cycle 1: `REVISE` by proof/security, contract/release, and UX/accessibility - reviewers; resolved by explicit `0.2.0` migration, versioned downstream - envelopes, deterministic race barriers, single-owner generated CLI - projections, strict entity grammar, exact proof routing, read-only release - allowlists, and state-specific browser oracles; -- cycle 2: `REVISE`; resolved by executable selector alignment, symmetric - closed input/output ABI trees, an explicit change-record v2 path, split proof - ownership, exact generator checks, relative/absolute symlink compatibility, - a legacy-term boundary, and a canonical whole existing-release block; -- cycle 3: two `APPROVE`, one `REVISE`; resolved by aligning the output-writer - selector and making generated CLI-contract freshness a mandatory - `npm run check` step; -- cycle 4: unanimous `APPROVE`; no P0-P2 design contradiction remains. -- cycle 5: unanimous `APPROVE` after correcting the generated stack-preset - topology; the single authored owner remains unchanged. -- implementation cycle 1: three independent `APPROVE` verdicts after - corrections C-01 through C-20, exact proof-selector closure, dead-helper - cleanup, and a green full worktree gate; no confirmed P0-P2 finding remains. -- committed-candidate cycle 1: two `REVISE` verdicts found a job-level - `continue-on-error` expression bypass and three untested per-view loading - states; C-21 and C-22 plus primary-owner projections closed both findings, - and the focused correction review returned three `APPROVE` verdicts. -- committed-candidate cycle 2: two `APPROVE` verdicts and one `REVISE` exposed - ignored inherited and step-level execution controls plus partial - `continue-on-error` evaluation in the generic workflow oracle; C-23 closed - workflow/job/step inheritance, exact environment entries, nullable scalar - key presence, required leaf jobs, and the aggregate, after which the focused - correction review returned three `APPROVE` verdicts. -- exact-commit cycle 1: proof/security and UX/architecture returned `APPROVE`; - contract/release returned `REVISE` because the `0.2.0` machine change record - omitted the intentional `adoption-doctor` blocked-state and exit-code - migration; C-24 closed the machine record, migration, rendered-note - falsifier, and proof binding, after which focused correction review returned - three `APPROVE` verdicts. -- exact-commit cycle 2: proof/security and UX/architecture returned `APPROVE`; - contract/release returned `REVISE` because the record and top-level-only - semantic test omitted the intentional non-enforced advisory rule transition - from `passed` to `skipped`; the first focused review exposed the same - undeclared transition outside the touched selection in `enforce-touched`. - C-25 declares the complete migration, strengthens both adjacent-rule oracles, - and binds them; the second focused correction review returned three - `APPROVE` verdicts. -- provider cycle 1: the exact candidate passed locally but two provider browser - attempts failed at the same 30-second Firefox cap on different tests while - Chromium and WebKit passed; the run lifecycle then deleted the promised - retained traces and the success-only upload skipped them. C-26 preserves - attempt-scoped failure diagnostics without creating or admitting passed - proof so the moving Firefox stall can be diagnosed rather than guessed; the - focused correction review returned three `APPROVE` verdicts after routing - both selectors exclusively to QUALITY-022. -- provider cycle 2: the first post-C-26 exact local gate reproduced the moving - Firefox timeout in two different state-matrix rows; both retained traces end - at the first `AxeBuilder.analyze()` evaluate of the 1,287,127-character - default axe source. A first minified-source candidate passed two full runs - but was falsified by a controlled repeated-audit run: minified and default - sources both stall under continuous trace screenshots, while the default - source completes 13 two-audit cycles without tracing and with action, DOM, - network, and source tracing when screenshots are disabled. A third full run - then falsified screenshot removal alone by stalling at the same large - builder evaluate. C-27 combines both necessary controls: pinned axe - initialization through the browser-context script channel, constant builder - loaders, and screenshot-free traces with one bounded best-effort post-failure - screenshot; the combination completed 30 consecutive two-audit Firefox - cycles. -- provider correction review: proof and UX returned `APPROVE`; contract - returned `CONDITIONAL APPROVE` after C-27 also closed the exact development - dependency allowlist, owner-to-binding non-claims, and constant-loader - reachability mutant. Two consecutive frozen-byte 72-test browser proofs and - direct proof verification passed without an intervening browser-input edit, - discharging the sole condition before final committed-object revalidation. -- final committed-object review: proof/security found that C-13's pinned-parent - publication could still follow a parent moved outside the repository after - the last route check. C-28 keeps temporary-file creation and cleanup on the - pinned parent but binds the irreversible publication to full source and - destination routes through the repository root; the exact pre-publication - falsifier covers outside-root and in-root replacements. -- final correction review: all three reviewers rejected C-28 because - `os.Root.Rename` resolves its source and destination routes separately and a - replacement can substitute the discoverable temporary basename. C-29 moves - the temporary object to the repository root, admits its identity and the - destination parent after the exact barrier, publishes through the repository - root, and narrows the owner contract to the strongest cross-platform claim - the implementation can prove. Adversarial same-user namespace mutation after - final identity admission is explicit rather than hidden behind another - check-then-use implication. -- C-29 correction review: the root-level candidate was rejected because it - regressed writable-child and nested-filesystem outputs, object identity did - not admit content, its compatible public contract was missing from the - release record, and final PR-body validation did not machine-check all - mandatory closeout facts. C-30 restores same-parent staging/publication under - the explicit concurrency boundary and admits identity, exact mode, and - digest; C-31 - projects the compatible addition into release notes; C-32 adds a canonical - closeout record validated in both reviewed and server-side body bytes. -- C-30 through C-32 correction review found that permission-only mode - comparison discarded setuid, setgid, and sticky bits, while C-32 validated - mutable artifact paths and later reread them for projection. C-30 now admits - the complete file mode exactly; C-33 copies each local evidence object once - into a private snapshot and validates and projects those same bytes. -- C-33 correction review found that snapshot byte identity does not admit - fields absent from its predicate. C-34 removes unvalidated artifact digests - from closeout so every projected local value is explicitly admitted. -- C-34 self-review found that streaming `jq -e` accepts a forged first JSON - document when a valid second document determines the final exit status. C-35 - slurps each snapshot, requires exactly one document, and admits the exact - browser-project inventory. -- The independent maximum-reasoning audit reproduced dynamically false - conditions on a required CI test step and the release candidate job that the - literal `false`/`0` deny-list admitted. C-36 closes both workflow inventories - with exact absent-or-owner condition projections. -- The C-36 correction review replaced the macOS smoke with an Ubuntu no-op - while retaining a successful job ID and reproduced vacuous bound negative - selectors on an invalid owner workflow. C-37 binds exact CI check names, - runners, and the platform-smoke command, rejects reusable-job substitution, - and requires positive owner admission inside the bound selectors. -- The C-37 correction review inserted a semantic shadow step before the exact - platform command, passed a mixed-type runner list through lossy - normalization, demonstrated that QUALITY-013 still omitted its positive CI - and release package-gate owners, and found a malformed closeout `jq` filter. - C-38 closes the ordered platform step inventory and package-script owner, - requires exact scalar runners, binds both positive package-gate owners, and - restores the executable singleton filter. -- The C-38 correction review moved the semantic shadow into the repository-local - setup action and added an otherwise-empty `run` key beside `uses`; both - survived path/value-only comparison. C-39 admits the exact local-action bytes - and makes `run`, `uses`, and `with` key presence part of the ordered step - inventory. -- The C-39 correction review returned three independent `APPROVE` verdicts - after reproducing the nested local-action shadow and null, empty, whitespace, - and dual execution-key mutants; no P0-P2 finding remains. -- A subsequent independent maximum-reasoning audit approved the exact C-39 - commit at 97/100 with no P0-P2 finding, but confirmed two P3 hardening gaps: - the closed selector inventory omitted the QUALITY-011 and QUALITY-013 - anti-vacuity scenarios, and README command extraction used whitespace - splitting that changed safe quoted Bash words. C-40 reopens exact-object - validation for both findings. -- C-40 focused review reproduced an owner-transfer mutant: changing only a - critical scenario's `requirementId` preserved its exact selectors and passed - the scenario-keyed inventory. C-41 keys every protected inventory by the - exact requirement/scenario pair and independently falsifies owner transfer. -- The same focused cycle reproduced complete selector deletion through the - generic empty-selector early return and NUL admission through the bounded - lexer, then found a plan threshold weaker than the final completion - criterion. C-42 moves exact-set admission before the empty path, rejects NUL, - and requires every final reviewer to report no unresolved confirmed finding. -- Continued boundary review found Unicode trimming changed literal Bash argv - and could erase invalid JSON NBSP bytes, escaped NUL bypassed the top-level - lexer check, and interactive Bash history expansion changed an admitted - double-quoted `!`. C-43 preserves exact non-IFS and JSON fence bytes, closes - escaped NUL, and rejects unescaped double-quoted history expansion. -- C-43 focused review then showed that symmetric space/tab trimming consumed - a valid trailing escaped delimiter before lexing. C-44 trims only leading - unescaped Bash delimiters and preserves trailing escaped space and tab. -- The first complete package-level run after C-44 showed that the pre-existing - missing-function mutant now stopped earlier at the new exact-set boundary. - C-45 moves that mutant to an unprotected binding so exact-set and generic - function-existence failures remain independently reachable. -- Continued review reproduced quoted and unquoted even-backslash history - literals rejected after pair collapse, one remaining P0-P2-only preparation - threshold, and repeated generic I/O during pure inventory mutants. C-46 - consumes complete backslash runs before `!` with Bash-equivalent quoted and - unquoted projection; C-47 aligns the last threshold; C-48 separates pure - inventory admission from generic AST and `go list` validation. -- Adversarial complexity review then showed that the first C-46 helper rescanned - a non-history backslash suffix after every collapsed pair, producing - quadratic work on a package-bounded README line. C-49 consumes every - backslash run once and dispatches its terminal byte under the same bounded - quoted or unquoted rules. -- The next complete package-level run showed that isolated generic - executability fixtures now stopped at the intentionally earlier global - inventory phase. C-50 exposes the two existing validation phases as separate - local functions: production composes both, while each fixture calls the - owner of the error it falsifies. -- Frozen C50 proof-routing review found that the generic missing-function and - invalid-signature falsifiers were not selectors of the QUALITY-010 - executability scenario. C-51 binds both and protects that complete - four-selector scenario with the same exact owner/set inventory. -- Frozen C51 review reproduced the same selective-route gap for the - QUALITY-013 permission-floor falsifier. A closed-world pass over the typed - package-gate oracle found ten owner-relevant tests outside its seven-selector - binding. C-52 binds the complete seventeen-test owner surface and protects it - with exact set equality. -- The frozen C52 correction review returned three independent `APPROVE` - verdicts with no confirmed P0-P3 finding. The reviewers independently - checked exact QUALITY-010, QUALITY-011, and QUALITY-013 inventories, bounded - README lexer semantics and linearity, JSON byte preservation, owner - separation, documentation parity, and absence of business-logic drift. -- The next exact-commit review confirmed two omitted boundary cases. The - intentional readiness-closeout character-reference verdict change was absent - from the closed release record, and the selector validator rejected an - executable Go test with an unnamed `*testing.T` parameter. C-53 closes the - release declaration, migration, rendered-note witness, selector grammar, and - exact QUALITY-010 and QUALITY-024 inventories. -- The repeated exact-commit review confirmed five further P2 gaps: entity - decoding preceded Markdown pipe parsing, the reachable specifications - no-match state was absent from the browser matrix, the removed synthetic - Arrow-key contract lacked migration disclosure, and the compatible pilot-all - envelope plus optional witness-selector I/O were absent from the release - record. C-54 closes all five at their existing owners. -- The next exact-commit UX review reproduced a discontinuous npm onboarding - route: root help displayed a bare executable while the installed consumer - admitted only local offline npm resolution, and the witness ignored the - displayed command. C-55 projects the copyable npm route and executes argv - parsed from that exact stdout while rejecting the bare-route mutant. -- C-55 correction review then reproduced Unicode-whitespace normalization: - leading or trailing NBSP around the displayed route passed the witness even - though Bash does not treat that byte as an IFS delimiter. C-56 removes only - the authored leading space/tab indentation and preserves both NBSP mutants. -- The next exact-commit review proved that the installed trace still - hard-coded every transition after root help, that the release record - omitted the onboarding addition and misclassified removed installed - governance paths, that absolute-symlink migration omitted manifest - ancestors, and that merge-critical workflow jobs admitted semantic shadow - steps. C-57 through C-59 close the displayed onboarding chain, exact release - declarations, migration scope, and complete ordered step inventories. -- The same review falsified A-04: the 3,025-line workflow oracle mixed five - independent requirement owners and the concentration had already hidden a - shadow-step gap. C-60 records a closed size ledger, splits peripheral owners - and neutral support, preserves the inseparable QUALITY-011/013 selector - cluster, and protects every moved binding by exact selector inventories and - stale-path mutants. -- The C-60 correction review confirmed six residual proof gaps: the closed - step projection omitted `id` and `timeout-minutes`; exact selectors did not - preserve their witness path; candidate staging could omit the five new - untracked owners; one ledger byte count was stale; the extracted scanner - selectors were not requirement-bound; and exact-tarball onboarding still - admitted bare invocation copy for ordinary leaves. C-61 through C-66 close - these gaps with presence-aware step fields, exact witness paths, closed - staging and size inventories, scanner owner bindings, and execution of every - displayed leaf-help route and installed invocation. -- Candidate-command rehearsal then showed that cleanup-bearing temporary-file - staging was not executable under the repository's destructive-action guard. - C-67 replaces temporary files and cleanup with in-memory exact inventories - and stdin pathspec admission while preserving every equality predicate. -- The next focused proof review removed the workflow permission floor and - added a surplus provider write scope without tripping the scanner selector; - its contract review also placed the installed block before Usage and used a - command-token prefix collision without tripping the onboarding verifier. - C-68 and C-69 close exact permission sets plus explicit inheritance and exact - Usage order, token boundary, and installed-byte equality. -- The C-69 review then deleted its new falsifier while all bound QUALITY-019 - selectors remained green. C-70 adds the selector to the owner requirement, - exact selector set, exact witness path, and deletion/surplus/owner/relocation - mutation inventory. -- The final C-61 through C-70 focused correction review returned three - independent `APPROVE` verdicts with no confirmed P0-P3 finding on one - unchanged frozen snapshot. Exact committed-object validation remains the - final publication precondition. -- Final provider-closeout rehearsal then proved that its browser snapshot - predicate still required the superseded 24-tests-per-project matrix while - the exact committed gate produced 25 per project. C-71 aligns the executable - closeout predicate with the current 75-test owner result before repeating - committed-object validation. -- The repeated exact-commit architecture review then reproduced overlapping - adoption output conditions: `--mode bootstrap` also described the agent and - materialization routes while the generator compared only condition text. - C-72 introduces one optional bounded machine condition model, closes its - finite normalized option space, binds concrete argv to exact conditions and - variants, and rejects repeated single-value selectors instead of retaining - last-write-wins ambiguity. -- C-72 decomposition then falsified the candidate-staging closure: moving the - pure condition algorithm out of the generator I/O owner created a sixth - decomposition-owner file, while P12.2 still admitted only the five earlier - workflow owners. C-73 closes that six-file owner subset and keeps exact - staged-path equality plus empty unstaged and untracked remainders. -- Direct argv falsification then showed that `--pilot ""` set the raw flag but - normalized to the same empty native value as omission, so the ABI condition - incorrectly reported `--pilot=absent` and selected the default first pilot. - C-74 rejects the empty valued selector, binds its exact diagnostic, and - includes the intentional rejection in requirements and migration guidance. -- Staging rehearsal then proved that a baseline-relative added-path inventory - is not the current untracked inventory after an earlier candidate commit: - five workflow files already exist in `HEAD`, while only the condition-model - owner remains untracked in the C-72 through C-74 amend. C-75 proves the - one-file current set separately and requires the six-file decomposition - subset to survive staging. -- Condition-closure review then showed that the 80-state test duplicated the - native mode and pilot literals. C-76 derives immutable test domains from the - same internal native lists that build `ValidateOptions` admission maps, while - retaining the exact current 80-combination and twelve-valid-state predicates. -- Claim review then showed that generic condition syntax does not imply generic - native-option closure: only the adoption output owner has the required - finite-domain and argv witnesses. C-77 admits that exact definition as the - only current condition-model owner and requires any later owner to add its - own native-closure proof before generator admission. -- Baseline-diff rehearsal then showed that the six decomposition owners are - only a subset of all added files: the candidate already contains seventeen - added files relative to the reviewed baseline and the condition owner makes - eighteen. C-78 closes the complete baseline-relative added-path inventory - independently from the current untracked set and owner subset. -- Independent C-78 review then found two proof escapes and one stale - architecture fact: the guidance mode/scope failure emitted JSON without an - exact output condition and variant assertion; an alias command or direction - could reuse the admitted definition; and the C-73 prose retained superseded - exact line counts. C-79 through C-81 bind the JSON error route, close the - command/direction/definition triple, and remove volatile inline measurements. -- C-79 correction review then removed both route coordinates from only the - guidance failure while the shared guidance condition kept the global count - green. C-82 makes JSON assertion and both route coordinates a biconditional - per case, eliminating that false-green path. -- C-82 review then removed the JSON assertion together with both coordinates; - the fixture biconditional remained true while runtime still emitted unchecked - JSON. C-83 binds the expectation to observed non-empty JSON stdout and closes - the exact fourteen-case JSON inventory. -- Final committed-object decomposition review then found that - `condition_model.go` duplicated the generator package's existing generic - sorted-map-key helper. C-84 reuses the same-package owner and removes the - redundant algorithm and import. -- Final committed-object proof review then found that the critical Mach-O - byte-compatibility scenario selected only a README projection test. C-85 - binds the exact negative, boundary-positive, truncated-parser, and legacy - parser witnesses and protects their selector/path inventory against - deletion, surplus, transfer, and relocation. -- The repeated exact-object review then found the same semantic-reachability - escape in the Python wheel-platform and one-shot browser cleanup scenarios: - both selected tests passed while their named operations had zero coverage. - C-86 binds the wheel owner/projection/verifier tests and the three cleanup - concurrency tests, then closes both selector and path inventories. -- Exhaustive review of all 105 candidate-added selector rows then found one - remaining semantic false route: the mutable-release-facts scenario selected - only package reference closure. C-87 binds the existing ten-case stale-fact - falsifier and closes its exact selector/path inventory. -- The independent Sol/max audit then found that exact permission maps on named - scanner jobs did not close the workflow job inventory: an unclassified job - with write authority remained admissible. C-88 requires each scanner - workflow's jobs to equal the advisory/provider union and preserves a surplus - write-job falsifier. -- Terminal UX review then executed the exact first command emitted by a stack - preset in the installed npm consumer and received `command not found`. - Initial C-89 review rejected a global npm renderer because the same binary is - shipped in the Python wheel. C-89 therefore admits one explicit immutable - invocation profile at the launcher boundary, renders npm, Python-module, and - direct-path continuations separately, and proves both installed channels. -- Terminal contract review then replaced the pilot aggregate output with an - object while its declared output witness still passed, and observed that - self-check's output witness asserted empty stdout on an input error. Review - also identified the root-distinct adoption aggregate and the app-owned pilot - union constructor. C-90 closes all three selector tuples, native-source - ownership, requirement bindings, and substitution falsifiers. -- Frozen implementation review then found that the Python executable could - carry report-visible secret-like or control content, and that help, - structured agent-route/workflow/coverage argv, project workflow identity, and - the installed wheel route chain remained outside the C-89 closed inventory. - C-91 closes launcher value admission; C-92 closes every owned display/argv - route while proving caller-owned argv preservation and direct-argv execution. -- Provider exact-object review then exposed two test-oracle portability gaps. - Linux could immediately reuse the inode of a removed temporary file, so the - writer correctly rejected the substitution as a mode change while the - identity mutant demanded a platform-dependent diagnostic. Separately, the - retained Firefox trace proved a fully rendered graph before one page-realm - bulk evaluation consumed the remaining 29 seconds without returning. C-93 - substitutes a pre-existing live file whose identity must differ while both - files coexist; C-94 replaces the bulk evaluation with retryable count plus - indexed-attribute assertions that are logically equivalent to exact ordered - array equality, without retries, timeout expansion, test splitting, or - assertion weakening. Independent architecture and UX review approved both - repair classes. -- Final measurement review found that the last static-analysis cleanup changed - `agentroute.go` after the threshold ledger was frozen. C-95 refreshes the - complete final ledger after every correction instead of treating a prior - exact snapshot as current evidence. -- The first exact-object Sol/max audit then falsified input-grammar closure: - typed workflow decoding silently discarded job-level execution controls, and - source hygiene omitted the shipped tracked CSS language. C-96 admits every - tracked workflow through closed raw workflow/job/step mappings with two - exact release-environment exceptions before typed semantics. C-97 derives - browser-asset extension mutants from the tracked owner inventory and adds - CSS without changing identifier-boundary matching. -- Dependency pre-merge validation then reproduced the Firefox 30-second stall - twice on the same immutable branch while the failing test moved between the - two selection scenarios. C-98 removes their repeated page-realm range - synthesis: collapse uses Playwright `selectText` and click actions, while the - Unicode case performs one locator-scoped exact-range operation with - independently computed strict bounds, without retries, a larger timeout, or - production hooks. -- The next exact-object Sol/max audit returned `REVISE` with four P2 findings - and no P0, P1, or P3 finding. It proved that publication commands conflated - the historical audit baseline with the current integration base, the - closeout predicate retained a stale scenario count, launcher admission - accepted bidi format controls, and `pilot-admission` exposed one undeclared - alias route plus last-write-wins selectors. C-99 through C-102 separate Git - identities, bind the exact final coverage count, close Unicode `Cc` and `Cf` - admission, and make every accepted pilot route declared and unambiguous. -- C-99 through C-102 review cycle 1 returned one `APPROVE` and two `REVISE` - verdicts; it required an exhaustive `Cc`/`Cf` oracle, two-phase Git/PR - identity, applicable closeout completion criteria, singleton-parent proof, - durable QUALITY-004 ownership, and compatibility declarations. Cycle 2 - returned one `APPROVE` and two `REVISE` verdicts because the migration text - incorrectly required exactly one pilot selector and hid the valid omitted - default-first route. Cycle 3 returned three independent `APPROVE` verdicts - on one frozen diff with no confirmed P1-P3 finding. -- The mandatory final exact-object Sol/max audit of - `e55bfc6e5641aed906d9a3c02e56a431bc0ca4b5` returned `REVISE` with one P2 - and no P0, P1, or P3 finding. The current release-record witness admitted - only a manually selected subset of the machine record, so semantic deletion - or a structurally valid surplus could remain green after regeneration. - C-103 closes the complete breaking, addition, migration, and rendered-note - inventories under the existing QUALITY-024 owner. -- C-103 review cycle 1 returned one `APPROVE` and two `REVISE` verdicts; - exact machine ID/order mutants and note-projection closure were added. - Cycle 2 returned two `APPROVE` verdicts and one `REVISE` because - section-local equality still admitted appended surplus, duplicate, or second - owned sections. Cycle 3 returned three independent `APPROVE` verdicts on one - frozen diff after complete ordered machine equality and one independently - authored byte-exact full-note projection closed every confirmed escape; no - confirmed P1-P3 finding remains. -- Publication rehearsal after the first C-103 terminal approval reproduced a - zsh refspec-expansion failure in the plan itself: `"$final_sha:refs/..."` - treats `:r` as a parameter modifier instead of a literal separator. C-104 - braces the variable before the adjacent colon and rejects every remaining - unbraced variable-colon occurrence in the tracked plan. -- C-104 focused review returned two `APPROVE` verdicts and one `REVISE`: the - exact braced refspec was correct, but the plan retained the lease preceding - the already successful first publication. C-105 preserves that value as - history and binds the next correction publication to the exact current - remote head. -- The mandatory new post-C-105 exact-object Sol/max audit returned `REVISE` - with one P2 and no other confirmed P0-P3 finding. The baseline-relative - added-file inventory still contained 18 paths even though three later - owner-test files increased the final set to 21. C-106 refreshed the 21-path - set at the C-106 freeze and made that epoch's two-file amend staging - sequence executable. -- The mandatory new post-C-106 exact-object Sol/max audit of - `4a828d1be9e3f9cab0e93d4ef5991fef0d2cd475` returned `REVISE` with two P2, - one P3, and no other confirmed P0-P3 finding. C-107 closes the public - Scorecard action's exact output-input set and adds the surviving surplus - mutant. C-108 restores the exact 30-requirement P10 invariant delta. C-109 - completes C-81 by removing all four volatile line-count qualifiers from the - reverse-decomposition rationale. -- C-107 through C-109 focused review returned one `APPROVE` and two `REVISE` - verdicts. C-110 rejects a string that the generic expression normalizer - previously converted into boolean truth and binds that exact substitution - mutant. C-111 time-indexes the two-file staging predicate to the C-106 epoch - so it cannot contradict the then-current C-107 through C-109 three-file - correction set. -- C-110 through C-111 focused review returned two `APPROVE` and one `REVISE` - verdict. The remaining design-history sentence still described C-106 as the - active freeze without a historical qualifier. C-111 now time-indexes that - sentence to the same C-106 freeze without changing either inventory. -- C-111 focused review cycle 3 returned two `APPROVE` and one `REVISE`. - C-112 closes the full Scorecard-action subset after a second differently - named action with surplus authority-bearing input survived the named-step - selector. -- C-112 focused review cycle 4 returned three `REVISE` verdicts. C-113 aligns - Scorecard repository identity with GitHub's case-insensitive owner/repository - semantics after a mixed-case second action survived the lowercase-prefix - classifier. -- C-113 focused review cycle 5 returned three `REVISE` verdicts. C-114 makes - repository admission explicitly ASCII before case folding after Unicode - simple-fold long-s aliases exceeded the provider identity domain. -- The first exact-provider attempt for - `81e2c7d570e1982ffe4a9f1e5a43150438017b41` passed source and macOS quality - but failed `quality / browser runtime` with 74 of 75 tests passing, which - caused `quality / required aggregate` to fail. The retained Firefox trace - proves that the rendered state and heading assertions completed before an - auxiliary `page.evaluate` version probe remained outstanding until the - unchanged 30-second test deadline; axe analysis had not started. Earlier - retained attempts timed out at distinct page-realm and locator operations, - evidence consistent with but not proof of one moving engine-level stall. - The exact Firefox/Juggler cause remains unverified. C-115 therefore treats - the provider failure as a falsification of C-27's bounded first-attempt - claim and makes removal of source-proven avoidable wrapper exposure the first - controlled correction hypothesis. -- C-115 design review cycles 1 through 5 returned `REVISE` while narrowing - causal claims, closing the combined default-rule and target-size falsifier, - naming every owner, preserving exact branding, adding frame/version/result - and operation-topology mutants, defining the empirical overturn condition, - and closing run-options plus test-engine identity. Cycle 6 returned three - independent `APPROVE` verdicts with no confirmed design gap. -- C-115 implementation review separated attempted from completed context and - page states, closed pending, failed, concurrent, and zero-retry mutants, - replaced an unsound source scanner with one fresh-page fixture, and removed - one redundant state assertion. Three independent reviewers approved exact - diff SHA-256 `4bf170fc8f5ea50619bc414badd88cd0997198e0419e2739357760c0e577d33f` - with no P0-P3 finding. -- The first immutable C-115 falsifier epoch used input digest - `sha256:8099d7060ba9033c1e8317b6032a8776ef21c879b371edda9a460732f66281f4`. - Firefox iterations 1 through 14 each passed 25 of 25 tests. Iteration 15 - passed 24 of 25 and stopped at the unchanged 30-second deadline in - `Locator.boundingBox()` after the locator had resolved a visible graph. - C-116 preserves the graph contract while removing that raw geometry call, - which had no narrower per-call bound and consumed the remaining test budget, - and two later raw operations that the failed attempt did not reach. -- C-116 design review cycle 1 returned three `REVISE` verdicts. The correction - narrows the theorem from effective raster usability to the local SVG and - owned-viewport contract, derives expected identities from the admitted HTTP - response rather than circular DOM metadata, closes descendant visibility, - alpha, and degenerate-edge mutants, removes a redundant production CSS - floor, defines exact CSS properties, separates review-byte freshness from - runtime-input freshness, and makes toolchain A/B explicitly conditional. -- C-116 design review cycle 2 returned one `APPROVE` and two `REVISE` - verdicts. The correction arms exact request/response observers before UI - activation and proves the single rendered response, names the complete data - and geometry owner chain, closes hidden rectangle/text paint, and excludes - cross-engine-incompatible line visibility assertions. -- C-116 design review cycle 3 returned two `REVISE` verdicts while the third - review was invalidated by a concurrent correction. Effective-alpha mutants - preserved opaque computed colors while setting node fill or stroke opacity - to zero, and an overlapping-node mutant preserved every identity, label, - paint, and edge predicate. The node contract now admits each opacity factor, - exact geometry, direct-child order, and local transform independently. -- C-116 design review cycle 4 returned three `REVISE` verdicts. CSS geometry, - individual-transform, motion-path, text-offset, and empty-label mutants - preserved exact SVG attributes or the singular `transform` property. The - contract now closes used rectangle geometry, the complete current transform - family, absent text-offset attributes, exact visible-label projection, and - positive font size. -- C-116 design review cycle 5 returned one `APPROVE` and two `REVISE` - verdicts. Hidden-overflow, individual zoom, content-visibility, and zero-dash - mutants survived the otherwise closed local predicates. The owned viewport - now preserves exact scrolling, and every admitted local graph element closes - zoom, content visibility, local effects, and edge dash paint. -- C-116 design review cycle 6 returned `REVISE` after text-anchor, baseline, - and font-adjustment mutants changed or erased visible glyph layout while - retaining content, coordinates, font size, and paint. Generic visibility - and viewport-intersection alternatives were independently falsified across - all pinned engines. The bounded text contract now admits the exact current - layout serializations directly. -- C-116 design review cycle 7 returned three `REVISE` verdicts. Alignment - baseline and hidden-`tspan` mutants survived; several retained text - conjunctions lacked independent falsifiers; `text-indent` was empirically - inert; and the generic-matcher counterexample used an overbroad quantifier. - The correction closes each effective text property independently, removes - the inert property, requires direct text-only labels, and states the exact - existential cross-engine failure. -- C-116 design review cycle 8 returned one `APPROVE` and two `REVISE` - verdicts. Text-security substitution and SMIL animation preserved base - content or geometry while changing glyphs or animated SVG values. The - correction closes text security, root and leaf element topology, and - animated-geometry surplus with isolated mutants. -- C-116 design review cycle 9 returned three `REVISE` verdicts. Fixture-equal - cached rendering survived response/DOM equality; independent retrying CSS - assertions admitted a phase-split animation with no jointly valid state; - and three new childlessness predicates lacked independent falsifiers. The - correction adds a response intervention sentinel, closes local CSS animation - and transition activity, names the temporal non-claim, and gives every leaf - topology predicate its own mutant. -- C-116 design review cycle 10 returned three `REVISE` verdicts. A positive - zero-duration transition delay, an animation on an ancestor that changed - inherited graph paint, and an external SMIL target all survived local - element checks; the exact group and root ordering predicates also lacked - independent reorder falsifiers. The correction closes the complete owned - ancestor chain, both transition dimensions, local SVG addressability, and - both order predicates with isolated mutants. -- C-116 design review cycle 11 returned one `APPROVE` and two `REVISE` - verdicts. Hidden graph tables preserved response/text equality while - removing the only visible authority, currentness, verification, and state - fields. Conversely, `xml:id` did not create an addressable SVG target in any - pinned engine and had no independent counterexample. The correction adds a - bounded local visibility and temporal contract for both graph tables and - removes that unjustified conjunct while retaining the reproduced plain-ID - external-SMIL falsifier. -- C-116 design review cycle 12 returned three `REVISE` verdicts. Transparent - text color, local filter/clip/mask effects, and Firefox external SMIL through - a table-cell ID preserved the first table contract while erasing trust-state - glyphs; static captions and headers and both transition dimensions also - lacked independent falsifiers. The correction closes local table paint and - addressability, exact static semantics, and every retained table conjunct - with separate mutants. -- C-116 design review cycle 13 returned two `REVISE` verdicts while the third - review was invalidated by the correction. Table font adjustment, zoom, - content visibility, and several order/count predicates lacked independent - mutants. More importantly, Firefox SMIL could target the existing - `#workspace-content` ancestor, proving that local ID bans were the wrong - boundary. The correction removes those bans, excludes declarative SVG - animation at the document boundary, and makes local topology falsifiers - inert and independent. -- C-116 design review cycle 14 returned three independent `APPROVE` verdicts - on git blob `598dfb89b7567df269b41491b15c7fe527248b3d` with no confirmed - P0-P3 finding. C-116 is approved for planning; no runtime success follows - until implementation and the fresh immutable 30-process epoch pass. -- The C-116 implementation candidate at - `0c67de58b0b9837d714e417f64758a76368f3efa` passed the complete immutable - replacement epoch. All 30 separate Firefox processes passed exactly 25 of - 25 tests with one worker, zero retries, zero skipped, unexpected, or flaky - tests, and `exited` watchdog status. Every record used input digest - `sha256:ec3d79218e20831e726bf45e171b1d0276fdf22a04790a13f1e72e6df8dbee0d` - and historical test-ID digest - `sha256:f7b80cd6ea950cad6693a7b11020f746581d6eba4f2b7314700e4161448a554c`; - the 30-record JSONL SHA-256 is - `e38754615878a012358d2fe75fd4af031107450a7ec2bc6d70db6bc89c543051`. - Both subsequent full browser proofs passed 75 of 75, the composite browser - gate passed 21 static tests and 75 runtime tests, and the final full - `npm run check` passed. All five outer watchdog records were `exited` with - leader exit code zero and empty signal and group-probe error sets. -- Provider run `30297044766`, attempt 1, reported pull-request head - `26e44b79a90b41494f9971b84f66e4b737bc9baa` and checked out synthetic merge - commit `da27a7a1b3e17a901a47621a31ca8ae3432f9901`. Both objects have tree - `ae3b0b16efc3d185425a91488b1f902eee630c2f`, so the executed bytes equal the - head bytes even though the commit identities differ. That run falsified the - implication from the local epoch to provider liveness. Firefox timed out - after entering Playwright 1.61.1 `evalOnSelectorAll` for the focus negative - control. Retained artifact `8665124396` has GitHub digest - `sha256:db3179664637de3b053bde5efce6b0e2e8b44e3d96c5b7bf07032a270b2b46b5`; - its report SHA-256 is - `3498361d22679cc87c6560c055750bb3c782bb1d8761b28e5287499e0486a4d2`, - its trace ZIP SHA-256 is - `b4f5560b9e0e240dab35e631d9b848a6f18817b02ca6c03c2254a32ba989328d`, - and the inner `0-trace.trace` SHA-256 is - `86a30f0e21dc41a9961d26506f262e18a1cfd8832cca24d9c70a1504864dc0a4`. - The trace contains the call's `before` record and no `after` record. - Chromium and WebKit passed, as did source, macOS smoke, CodeQL, OSV, and - semantic diff. This is not evidence of a product or focus-contract failure. - It activates the already approved isolated Playwright 1.61.1-versus-1.62 - A/B overturn condition. Bot PR 80 run `30250528617`, against base - `3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64`, checked out merge - `3367101eaf48fd664f1c1975181c15d047d7fac2`; browser artifact `8646807702` - has GitHub digest - `sha256:57de6b30f9a3a82ca33b4ad18f9f36c2b47dbc55dac301c4f11669040b7a4ae1`. - Its 1.62 provider browser job passed only the older six tests per engine and - therefore did not exercise the current focus negative control; its source - job failed package verification because it changed only the manifest and - lock. The admitted A/B therefore changes exactly the manifest, lock, - package-verifier pin, and verifier fixture together; it does not change - runtime tests, retries, timeouts, production code, or business logic. -- The first Playwright 1.62 post-epoch full gate exposed C-117 under a retained - `TMPDIR` whose group was `wheel`, outside the caller's supplementary groups. - The `mode-setgid` test mutation treated successful `chmod` return as proof - that the bit materialized. Darwin instead returned success while clearing - setgid, so the writer correctly observed unchanged mode `0644` and published. - The same subtest passed 100 of 100 under the default `staff`-group temp root - and failed 100 of 100 under the retained `wheel`-group root. This confirms a - test-oracle precondition defect, not a production-writer defect. -- Final exact-object review found that C-119 introduced terminal HTTP-response - and exact accessible-name guards without guard-specific falsifiers. Removing - either guard left the 81-test matrix green. C-120 adds owner-local open and - reload 503 counterexamples plus a substring-preserving heading-name drift; - all three engines must reject those mutants before provider validation - repeats. -- Provider attempt 1 for exact source commit - `dcc824b31f858ab8fea5be683e5d81f12f039279` falsified C-119's remaining - `page.goto(..., {waitUntil: "commit"})` dependency. Firefox received the - main document with status 200, loaded every local asset and API response, and - rendered the initialized workspace, but the runner never resolved the - navigation call. C-121 replaces provider-falsified lifecycle waiting with a - pre-armed exact-URL navigation-response observation, exact trigger token, - successful-response admission, and the existing exact semantic heading - assertion. A same-URL non-navigation 503 decoy makes the navigation - classifier executable rather than structural. -- Final Sol/max review of exact candidate - `c2315fdf28be95eab08089008773d7dd234d9c96` found two false-green - candidates. C-122 reconciles P12.2's correction inventory so its - declaration and executable post-`c2315fd` three-path set have one value; - the preceding four-path staging set is historical. C-123 adds a - negative truth table for every raw base-URL admission clause; source presence - alone did not prove that the runtime witness depended on those clauses. -- The same review then reproduced C-124: deleting exact trigger-token - admission and pending-waiter abort/consumption left static 22/22 and runtime - 93/93 green. One deterministic injected pending waiter now distinguishes the - exact token, signal abort, and explicit rejection-consumption paths without - adding a browser lifecycle or production seam. - -## Purpose - -This document defines the smallest owner-valid repair for every finding that -survived independent reproduction and adjudication of the July 2026 -architecture, proof, security, release, and usability audits. - -It is temporary change authority, not a new product specification. Existing -requirements, machine contracts, code owners, and executable falsifiers remain -the durable owners. - -## Scope - -In scope: - -- false-positive proof states and false-green local oracles; -- filesystem confinement and immutable-read boundaries; -- public CLI input, output, exit, diagnostic, and discovery contracts; -- release SBOM semantics and pre-1.0 consumer compatibility; -- package and self-hosting negative-test closure; -- install-to-first-success onboarding; -- browser error handling, accessibility, responsive layout, and contrast; -- package-public documentation closure; -- exact requirement, binding, witness, and non-claim updates needed by these - repairs. - -Out of scope: - -- provider-side publication, branch protection, Trusted Publisher, registry - identity, or production rollout; -- new commands, remote services, SDKs, retry systems, or policy engines; -- general rewrites of command packages; -- decomposition based only on line count; -- full GitHub Actions interpretation; -- full CommonMark parsing; -- a claim of complete WCAG 2.2 AA conformance. - -## Authority - -The repair is bounded by: - -1. `AGENTS.md`, especially evidence-class separation, immutable admission, - package boundaries, and closeout; -2. `BACKLOG.md` for active, blocked, and deferred claims; -3. `NON_CLAIMS.md` and `ADOPTION.md`; -4. `docs/release-process.md` and `docs/proofkit-contract-map.md`; -5. the five `docs/specs/*/requirements.v1.json` sources; -6. `proofkit/cli-contract.v2.json`, - `proofkit/command-families.v1.json`, - `proofkit/requirement-bindings.json`, and `proofkit/witness-plan.json`; -7. command-local admission code and native tests for their exact behavior. - -When these surfaces conflict, the repair fails closed and updates the durable -owner rather than preserving a convenient implementation. - -## Retirement - -This document retires only after all accepted rows below are represented by -durable requirements or contracts plus executable falsifiers, the final -committed implementation passes `npm run check` and exact-object review, the -validated branch and an open, unmerged pull request identify that same object -against the same reviewed base commit, and the following closed source-owned -provider-check inventory is present for that head/base pair in the workflow -run for pull request `#78`, provider attempt `1`, with `success` conclusions: - -- `ci` / `quality / source`; -- `ci` / `quality / platform smoke / macos-15`; -- `ci` / `quality / browser runtime`; -- `ci` / `quality / required aggregate`. - -Every other check run and legacy status context observed for the object in two -consecutive final provider passes must have an accurately reported terminal -disposition. Retrospective routing must then be complete, and the final -pull-request body must record those facts and residual non-claims. A final -bounded readback must verify the remote head and base SHAs and pull-request -identity before, between, and after two reads of provider conclusions from -literal-SHA workflow-run, job, check-run, and commit-status endpoints, and must -compare the two canonical provider projections byte for byte. The reviewed -body must contain exactly one record of that projection's SHA-256 and exactly -one sentinel-delimited canonical JSON projection equal to the observed bytes; -any prose is non-authoritative. The server-side body must equal the reviewed -local body byte for byte. The inventory is owned by the exact required job set -in -`.github/workflows/ci.yml` and its typed workflow oracle; it is a closeout -requirement, not a claim about provider-side branch-protection settings, -an atomic provider snapshot across endpoints, concurrent PR-identity history -between bounded observations, or provider immutability after the final -response. Until every condition holds, this document remains temporary -closeout authority. After retirement it must not be cited as runtime, release, -or merge authority. - -## Formal quality model - -Let: - -- `A(x)` mean caller input `x` was structurally admitted; -- `S(x)` mean a semantic report was produced from admitted input; -- `B(x)` mean an unresolved blocked precondition is present; -- `C(p, h)` mean path `p` was opened through confined root handle `h`; -- `I(f0, f1)` mean two observations refer to the same opened file identity; -- `G(e)` mean a workflow guard is one exact owner-admitted expression; -- `R(j)` mean required leaf job `j` completed with `success`; -- `Reach(s0, sn, w)` mean witness trace `w` executes every transition from - installed state `s0` to first-success state `sn` with the same artifact; -- `X(v)` mean UI state `v` has an executable accessibility and reflow oracle. - -The repaired system must satisfy: - -```text -I-01 B(x) -> report.state = blocked and exit != 0 -I-02a not A(x) and not envelope_mode -> exit = 1 and stdout = empty and stderr != empty -I-02b not A(x) and envelope_mode -> exit = 1 and stdout = one invalid-input envelope and stderr = empty -I-03 A(x) and S(x) -> stdout = one admitted JSON value and stderr = empty -I-04 read(p) -> exists h,f: C(p,h) and bytes = read(f) and I(f_pre,f_post) -I-05 publish(p) -> every parent, temporary file, and rename is rooted at one h -I-06 workflow_guard_accepted(e) <-> G(e) -I-07 aggregate_success <-> for all required jobs j: R(j) -I-08 one_commonmark_entity_decode(a) = b -> phrase_class(a) = phrase_class(b) -I-09 exists w: Reach(local_install, first_valid_input, w) -I-10 stable_browser_state(v) -> X(v) -I-11 release_dependency_edge(a,d) -> d is required by artifact a -I-12 breaking_pre_1_0_release -> exact_pin_policy and non-patch version change -I-13 blocking_conjunction(f1..fn) -> every fi has an independent falsifier -I-14 public_contract_change -> canonical compatibility projection changes -``` - -No row is complete merely because a positive test passes. Each row requires a -negative case that accepts the current wrong implementation and rejects the -repair's forbidden alternative. - -## Adjudicated finding ledger - -### Accepted findings - -| ID | Severity | Finding | Durable owner | Required outcome | -|---|---:|---|---|---| -| R-01 | P1 | `adoption-doctor` reports blocked prerequisites as passed outside enforcing modes | `REQ-PROOFKIT-RETIRE-008` | Blocked evidence is always blocked; ordinary advisory gaps retain non-enforcing behavior | -| R-02 | P1 | CycloneDX root dependency edges include tool/build modules not required by shipped artifacts | `REQ-PROOFKIT-QUALITY-002` | Artifact dependency edges contain only evidenced runtime requirements; retained inventory is explicitly excluded; digest and build information come from one immutable byte snapshot read through an identity-checked pinned descriptor | -| R-03 | P1 | TypeScript public API scanner validates a pathname and later reopens it | `REQ-PROOFKIT-PACKAGE-002` | A confined repository root opens a pinned package-root handle before its manifest and sources; pinned file handles own identity, bytes, and cache keys | -| R-04 | P1 | Repository-relative output checks parents by pathname before independent create and rename operations | `REQ-PROOFKIT-SPEC-009` | Parent creation remains repository-root-confined; temporary write, cleanup, and atomic publication use one pinned destination parent after final parent-route plus temporary-object identity, exact mode, and content admission | -| R-05 | P1 | Workflow guard oracles accept expected substrings inside semantically false expressions or drop execution-control fields that alter an exact command | `REQ-PROOFKIT-QUALITY-013` | Trust-significant expressions use exact whole-expression allowlists and merge-critical jobs and steps admit only the modeled safe execution controls | -| R-06 | P1 | CI aggregate oracle accepts expected shell tests inside dead or neutralized code or under an inherited/local execution override | `REQ-PROOFKIT-QUALITY-011` | The exact required job set, execution controls, and canonical aggregate script are admitted | -| R-07 | P1 | Nine commands encode structural admission failure as report JSON and leave `stderr` empty | `REQ-PROOFKIT-QUALITY-004` and CLI process contract | Ordinary malformed input is an error; admitted semantic failures remain reports | -| R-08 | P2 | Readiness phrase scanning misses single-decoded HTML character references | `REQ-PROOFKIT-PACKAGE-002` | Phrase comparison uses one semantic-text decode after structural row parsing | -| R-09 | P2 | The CLI compatibility hash omits input and output contract semantics | `REQ-PROOFKIT-QUALITY-004` | Every required-input command has a bounded contract projection included in ABI compatibility | -| R-10 | P2 | A breaking pre-1.0 patch remains range-compatible for caret consumers | release change record and `REQ-PROOFKIT-QUALITY-024` | Consumer docs require exact pins and release validation rejects an incompatible patch policy | -| R-11a | P2 | Self-hosting report verdict lacks isolated falsifiers | `REQ-PROOFKIT-PACKAGE-004` | A pure verdict boundary rejects nonzero, invalid JSON, and non-passed reports | -| R-11b | P2 | Wheel version, identity, and SHA checks lack isolated falsifiers | `REQ-PROOFKIT-PACKAGE-006`, `REQ-PROOFKIT-QUALITY-023` | Each wheel integrity predicate has its own negative case | -| R-11c | P2 | Root tarball deny-list decisions lack operation-boundary falsifiers | `REQ-PROOFKIT-PACKAGE-001` | A complete tarball plus one forbidden entry fails for every denied class | -| R-11d | P2 | Local and CI receipt identity lacks an isolated falsifier | `REQ-PROOFKIT-PACKAGE-004` | Local and CI identities cannot collapse into the same receipt | -| R-12 | P2 | Four command-route closure conjuncts lack isolated falsifiers | `REQ-PROOFKIT-QUALITY-010` | Each closure field has its own negative case | -| R-13 | P2 | External action SHA pinning is correct but unguarded | `REQ-PROOFKIT-QUALITY-025` | Every non-local `uses` value is exactly a 40-lowercase-hex commit | -| R-14 | P2 | Installed README commands assume a globally resolvable executable | `REQ-PROOFKIT-PACKAGE-003`, `REQ-PROOFKIT-QUALITY-019` | Canonical npm onboarding uses offline local package resolution | -| R-15 | P2 | Stack preset IDs are not projected into direct help or the machine contract | `REQ-PROOFKIT-SPEC-018` | Runtime, help, diagnostics, and contract share one preset inventory owner | -| R-16 | P2 | No copyable first valid requirement input is routed from Start Here | `REQ-PROOFKIT-SPEC-001` | A marker-bounded shipped example is executed by a test | -| R-17 | P3 | Raw pipe characters break the first contract-map decision row | contract map | Every decision row renders as exactly three GFM cells | -| R-18 | P2 | Axe runs in only one terminal workspace state and does not execute `target-size` | `REQ-PROOFKIT-QUALITY-022` | A stable-state matrix runs default axe plus explicit target-size | -| R-19 | P2 | Initial workspace markup uses invalid or unjustified ARIA roles | `REQ-PROOFKIT-SPEC-021` | Native list/article semantics replace the synthetic tree; handoff output has a labelled region | -| R-20 | P2 | Workspace produces document-level overflow at 320 CSS pixels | `REQ-PROOFKIT-QUALITY-022` | The document reflows; only graph/table viewports scroll internally | -| R-21 | P2 | Native control colors do not prove text, boundary, and focus contrast | `REQ-PROOFKIT-QUALITY-022` | Explicit light/dark tokens and computed contrast oracles cover pinned engines | -| R-22 | P3 | Root help does not reveal command-family discovery | `REQ-PROOFKIT-SPEC-018` | Root help contains the one opt-in `help families` route | -| R-23 | P2 | Workspace has no bounded visible bootstrap failure state | `REQ-PROOFKIT-SPEC-021` | Loading and sanitized terminal failure states exist before and after manifest admission | -| R-24 | P2 | `baselineVerification` overstates caller-expected digest coverage | `REQ-PROOFKIT-SPEC-019`, `021`, `022`, and `023` | Schema v2 calls the field `expectedDigestCoverage`; every context consumer owns its v2 projection, and v1 is admitted only through an explicit adapter | -| R-25 | P2 | Python and supported-platform onboarding is incomplete | `REQ-PROOFKIT-PACKAGE-006`, `REQ-PROOFKIT-QUALITY-016`, `REQ-PROOFKIT-QUALITY-023` | Docs project package metadata without claiming registry availability | -| R-26 | P2 | Package-public docs reference `BACKLOG.md`, but the npm artifact omits it | `REQ-PROOFKIT-PACKAGE-001` | Contributor-only files and backlog routes are removed from the npm artifact | -| R-27 | P3 | The browser render diagnostic omits the supported `workspace` view | `REQ-PROOFKIT-SPEC-009` and `021` | Runtime and app diagnostics enumerate the same admitted view vocabulary | -| R-28 | P1 | Existing GitHub Releases can receive missing assets through a backfill branch | `REQ-PROOFKIT-QUALITY-025` | Existing release topology is validated without provider mutation | -| R-29 | P2 | `SPEC-021` says no command execution while `--open` invokes a fixed OS browser launcher | `REQ-PROOFKIT-SPEC-021` | The owner distinguishes fixed loopback launch from caller-supplied or witness execution | - -### Reclassified or rejected hypotheses - -| ID | Disposition | Reason | -|---|---|---| -| A-01 | Rejected as a global defect | Duplicate canonicalization and strict sorted-unique admission are different contracts. Replacing every local helper would change valid set semantics without a field-level owner proof. | -| A-02 | Reclassified from P1 security to R-24 P2 naming | Caller-authored expected/current digest equality proves self-consistency only. Existing owners already deny freshness and provenance; the term, not an authentication boundary, is wrong. | -| A-03 | Rejected | No `jsonNumber` exploit or divergent admitted value was reproduced. | -| A-04 | Confirmed and remediated | `scripts/workflow_package_gate_oracle_test.go` was 3,025 LOC / 94,323 bytes and mixed five independent requirement owners; the missed semantic-shadow step proves material review harm. C-60 separates browser, runtime-precondition, workflow-source, security-scanner, and neutral-support surfaces while retaining the logically inseparable QUALITY-011/013 cluster. | -| A-05 | Rejected | No prematurely decomposed package was proven. Kernel dependency direction is acyclic and command packages own distinct public routes. | -| A-06 | Rejected | Current action references are already exact commit SHAs. Only the missing falsifier remains as R-13. | -| A-07 | Rejected | A producer marker embedded in a caller-computable snapshot ID cannot create provenance and would add false authority. | -| A-08 | Rejected | Current-build self-consistency cannot alone become merge-critical proof; no repair may introduce this implication. | - -## Design decisions - -### D-01: Evidence state classification - -Problem: - -`adoption-doctor` derives both record state and rule state from policy-enforced -gaps, erasing the epistemic fact that a prerequisite is blocked. - -Chosen owner boundary: - -Keep gap discovery in `adoptiondoctor`; add a classification function that -separates unconditional blocked gaps from policy-enforced advisory gaps. - -Design: - -```text -blocked = gaps where kind is blocked_precondition or child_report_blocked -enforced = policy-selected non-blocked gaps - -if blocked is non-empty: state=blocked, exit=1 -else if enforced is non-empty: state=failed, exit=1 -else: state=passed, exit=0 -``` - -Blocked rule rows are always `blocked`. Other non-enforced rows use -`adoptionmode.NonEnforcingStatus`. Promotion readiness consumes both sets. - -Rejected lower-cost alternative: - -Changing only the rule status leaves the top-level machine state false. - -Proof invariant: - -`blocked gap -> blocked report`, while an observe-mode advisory candidate -remains `passed` with a `skipped` rule. - -Non-claims: - -The report does not authenticate the prerequisite, execute evidence, or approve -enforcement. - -Rollback or overturn condition: - -Only a durable owner explicitly redefining blocked as a policy warning may -overturn this decision. - -Why this avoids accidental complexity: - -It adds one classification boundary and reuses the existing mode vocabulary. - -Why this avoids premature over-decomposition: - -The logic remains in the command owner. - -### D-02: Artifact-honest SBOM - -Problem: - -`go list -m all` is a source/build inventory, not proof that each module is a -runtime dependency of every package, wheel, and binary. - -Chosen owner boundary: - -`internal/tools/releasesbom` continues to own deterministic release SBOM -generation. Release files remain subject components. Go module inventory is -retained only as explicitly excluded build inventory unless an individual -release binary provides a runtime dependency edge. - -Design: - -- add CycloneDX `scope`; -- mark `go list` module inventory as `excluded`; -- add `proofkit:evidence-class=source_build_inventory`; -- do not include excluded modules in the root `dependsOn`; -- retain release-file components as release subjects/representations, not - runtime dependencies, and keep them out of root `dependsOn`; -- emit artifact-specific edges from each binary BOM reference to only the - runtime modules recovered from that binary's build information; -- when a runtime module also exists in source inventory, deterministically - deduplicate by package URL and promote the component to `required`; source - evidence remains a property rather than a second conflicting BOM reference; -- package and wheel components receive no runtime edge without their own - artifact-derived evidence; -- if `debug.BuildInfo` or `go version -m` exposes runtime dependencies for a - future non-stripped artifact, emit those through a separately tested runtime - inventory function rather than inferring them from the module graph; -- test that tool-only modules can never become root runtime edges. - -Rejected lower-cost alternative: - -Deleting all Go module records hides useful supply-chain inventory. Keeping -them unscoped preserves the false dependency claim. - -Proof invariant: - -Every binary dependency edge has evidence from that binary; source/tool -inventory and distribution representations have no runtime dependency edge. - -Non-claims: - -The SBOM does not prove vulnerability absence, license approval, reachability, -or provider attestation ingestion. - -Rollback or overturn condition: - -A reviewed release owner may replace excluded inventory with artifact-derived -runtime edges after adding cross-platform falsifiers. - -Why this avoids accidental complexity: - -It corrects the evidence class without introducing a linker graph framework. - -Why this avoids premature over-decomposition: - -All logic stays in the current SBOM tool. - -### D-03: Handle-anchored filesystem operations - -Problem: - -Containment checks and later pathname operations do not imply object identity. - -Chosen owner boundary: - -Use Go's `os.Root` independently in the public API scanner and the output -writer. Do not create a generic filesystem abstraction because the read and -publication contracts differ. - -Scanner design: - -- admit lexical repository-relative paths; -- open the repository once with `os.OpenRoot`; -- open each referenced package as a confined pinned sub-root before reading - its package manifest, then read every source for that package through the - same sub-root; -- open each admitted lexical path through its owning root and pin its file - handle; -- resolve its canonical in-root path, open that path through the same root, and - require `os.SameFile` between the two pinned handles before accepting the - canonical extension; this preserves safe in-root symlinks without a - check-then-reopen implication; -- relative symlinks whose targets remain inside the root are preserved; - absolute symlink targets are rejected by `os.Root` even when they point back - inside the repository. This is an intentional `0.2.0` security-hardening - change recorded in the CLI contract, release change record, migration text, - and positive relative-link/negative absolute-link tests; -- bind pre/open/post identity and size around the bounded read, cache each - lexical admission immutably, and bind every alias of one canonical source - route to the identity and digest of its first admission; reject any later - alias whose identity or digest differs; -- derive extension and package directory from the admitted object and pinned - sub-root, never by a second unconfined open. - -The scanner implementation exposes a private operation seam used only by -same-package tests. A staged barrier deterministically pauses after the legacy -path check but before the legacy reopen, proving the current redirect -counterexample without time-based race loops. The repaired path is then tested -with the same barrier and pinned handles. - -Writer design: - -- open the current repository root once; -- create/check parents with root-relative operations; -- pin the admitted destination parent; -- create a random temporary file through that pinned parent using - `O_CREATE|O_EXCL`; -- write and chmod to `0644` through the open temporary file handle, close it, - and retain its admitted file identity; -- immediately before publication, expose a deterministic test-only object - barrier, re-admit the non-symlink temporary entry's identity, exact mode, and - content digest, expose a second test-only barrier, then re-admit the current - destination-parent route at the irreversible rename boundary; -- rename the temporary source to the destination through the same pinned - parent, preserving baseline writable-child and cross-filesystem behavior; -- clean up the temporary route through the same pinned parent; -- reject symlink or directory destinations without relying on the check for - confinement. - -The writer receives the same style of same-package operation seam: a test -barrier performs the parent substitution before temporary-file creation, -before object admission, and after object admission at the irreversible rename -boundary, while a sibling case replaces the temporary route, rewrites its -content, or changes its permission or special mode bits before final object -admission. External and in-root replacement sentinels, stable-path bytes/mode, -temporary identity, exact-mode, and content rejection, absence of published -output, and absence of temporary residue are asserted. Polling races and -probabilistic swap loops are forbidden as proof. - -Rejected lower-cost alternative: - -Additional `Lstat`, `EvalSymlinks`, or string-prefix checks leave a race window. - -Proof invariant: - -Concurrent parent or source replacement cannot read or mutate an external -sentinel. - -Non-claims: - -The writer does not promise protection from adversarial concurrent content or -namespace mutation by the same operating-system user during the operation, -fsync durability, or a repository-wide transaction. -The scanner does not prove checkout freshness. - -Rollback or overturn condition: - -Unsupported `os.Root` platform behavior or an owner-required symlink workflow -must be resolved with an equally strong descriptor-based implementation, never -with pathname rechecks. - -Why this avoids accidental complexity: - -It uses the standard confined-root API and command-local helpers. - -Why this avoids premature over-decomposition: - -No new shared package is admitted. - -### D-04: Exact workflow source oracles - -Problem: - -Substring recognition is not semantic implication for either expressions or -shell programs. - -Chosen owner boundary: - -The fixed repository workflows are checked by exact, owner-reviewed source -forms. `actionlint` retains syntax/expression validation. - -Design: - -- normalize only layout whitespace outside quoted expression literals; -- compare every trust-significant `if` against a complete allowed-expression - set for its specific job or step; -- require exact required-job `needs`; -- require the aggregate workflow to retain exact `bash` run defaults without - an inherited working-directory override or environment entries; -- require every required leaf job and the aggregate job to omit job defaults - plus job-level `continue-on-error` and have no job environment entries; -- require every step in those jobs to omit shell, working-directory, and - `continue-on-error` and have no step environment entries; -- require the aggregate job to have exact `always()` admission and one run step - with no step-level `if` or `uses`; -- for generic package-gate workflows, admit only absent or exact safe workflow - run defaults, exact owner-reviewed workflow and step environments, no - gate-job defaults or environment, and no execution override on any step in - the gate job; -- compare the whole aggregate shell program to a canonical constant; -- require every external `uses` reference to match a 40-lowercase-hex SHA, - exempting repository-local `./` actions; -- give this supply-chain property a dedicated requirement rather than - overloading the actionlint requirement, which owns syntax and expression - validity but does not claim external action safety. - -Rejected lower-cost alternative: - -Growing deny-lists cannot close expression or shell grammar. - -Proof invariant: - -`|| true`, dead branches, quoted predicate text, early success, background -tests, inherited or local environment, shell, and working-directory overrides, -unexpected environment entries, and any schema value including explicit YAML -`null` presence for the forbidden scalar job/step controls all fail the oracle. - -Non-claims: - -The local source oracle is not a GitHub Actions interpreter and does not prove -provider execution or branch-protection configuration. - -Rollback or overturn condition: - -Any workflow expression change requires an explicit owner review and an -allowlist update with a new negative case. - -Why this avoids accidental complexity: - -Exact forms are stronger and smaller than a partial evaluator. - -Why this avoids premature over-decomposition: - -The existing workflow test remains the owner. - -### D-05: Structural admission versus semantic failure - -Problem: - -Some builders translate structural errors into ordinary report records, -contradicting the CLI process contract. - -Chosen owner boundary: - -Command packages return admission errors. The app layer owns channel routing. -Only explicitly requested agent-envelope modes convert an admission error to a -JSON invalid-input envelope. - -Design: - -```text -ordinary structural error: - exit 1; stdout empty; sanitized stderr diagnostic - -admitted semantic failed or blocked report: - nonzero exit; stdout one JSON report; stderr empty - -explicit agent-envelope invalid input: - exit 1; stdout one invalid-input envelope; stderr empty -``` - -The nine reproduced commands receive a table-driven ABI falsifier. Builder -signatures are changed only where needed to preserve this split. - -Rejected lower-cost alternative: - -Duplicating the same diagnostic in both channels makes machine composition -ambiguous. Detecting synthetic `invalid-input` report IDs in the app preserves -the wrong owner direction. - -Proof invariant: - -Every required-input command satisfies the same channel algebra for `{}` and -for one admitted semantic failure. - -Non-claims: - -Human diagnostic bytes are stable only where explicitly included in the ABI -corpus. - -Rollback or overturn condition: - -A versioned CLI process contract may add another explicit projection; command -packages may not silently invent one. - -Why this avoids accidental complexity: - -It removes a translation layer instead of adding one. - -Why this avoids premature over-decomposition: - -Channel behavior stays centralized in `internal/app`. - -### D-06: Machine-readable CLI compatibility - -Problem: - -The public contract claims input ownership but 59 required-input commands have -no input contract, and the ABI projection ignores both input and output -contracts. - -Chosen owner boundary: - -`proofkit/cli-contract.v2.json` is the single authored machine owner. A -deterministic checked generator projects contract metadata into private Go -tables used by descriptors and help. Command admission code remains the -implementation and must be linked to named native admission witnesses. - -The generator is `internal/tools/commandcontractgen`; it writes two -owner-derived private projections: - -- `internal/app/command_contract_generated.go` for app descriptors/help; -- `internal/command/stackpreset/preset_ids_generated.go` for the lower-level - preset package. - -The second output prevents `stackpreset` from importing `internal/app` and -therefore prevents an import cycle. Both files are generated from the same -authored CLI contract in one invocation and have no independently editable -vocabulary. Freshness is checked by: - -```bash -npm run command-contract:check -go test ./internal/tools/commandcontractgen ./internal/app -run CLIContract -``` - -`package.json` defines `command-contract:check` as -`go run ./internal/tools/commandcontractgen --check` and includes it in the -mandatory `npm run check` chain before Go/package closeout. - -Design: - -- use a deliberately bounded `root_shape_only` definition grammar: each - direction declares sorted, condition-complete variants with a root kind - (`object`, `array`, or explicitly unconstrained `json_value`), exact - top-level allowed/required fields for object roots, and exact CLI flag/mode - conditions; a direction with multiple bounded root kinds declares - `rootType=union`, while `json_value` is forbidden as a union escape hatch; -- the adoption output definition may opt into - `cli_flag_conjunction_v1`; every condition then uses one canonical - ASCII-space-separated conjunction over the same sorted flag dimensions, - every dimension has either exact absent/present states or exact literal - values, every dimension is an allowed command flag, and assignments owned by - different variants are pairwise disjoint; a second definition is rejected - until its own native-domain and argv-closure witness is admitted; -- `internal/tools/commandcontractgen/condition_model.go` owns that pure grammar - and disjointness algorithm; generator I/O and projection remain in `main.go`; -- every required-input command receives an `inputContract` containing: - `schemaVersion`, the bounded root-shape definition and canonical digest, - owner refs, exact native admission witness selector, and an explicit - non-claim for nested fields, scalar types, collection cardinality, - nullability, and cross-field semantics; -- every JSON output command receives an `outputContract` whose variants cover - every supported JSON-producing flag/mode route, including object/array - unions and agent-envelope projections; -- the generated private Go projection supplies the bounded direct-help summary - and flag choices; these values are not manually repeated in descriptors; -- the canonical ABI projection includes each bounded input/output definition, - canonical digest, scope, flags, flag choices, and output modes; -- generator checks require complete required-input and JSON-output coverage, - generated-file freshness, sorted and unique root variants, non-empty exact - conditions, valid root-kind/field combinations, and an executable native - witness selector; -- native source digests remain conservative freshness sentinels only: they - deliberately force review after owner-package code changes but are not - semantic-equivalence proof; -- direct public-CLI tests exercise every high-risk multi-mode route and assert - its root kind and exact root keys; command-local witnesses remain the - evidence for individual native admission and output behavior; -- the adoption output condition oracle enumerates the finite - `5 modes x 2 agent states x 2 materialization states x 4 pilot states` - domain derived from immutable copies of the same internal native value lists - that build option-admission maps, requires native option admission to accept - exactly the same twelve assignments declared by the condition model, derives - each exercised condition from parsed argv, and rejects repeated `--mode` or - `--pilot` selectors before they can change the selected assignment; -- contract mutation tests cover root kind, allowed/required fields, variant - conditions, schema versions, deleted definitions, digests, selectors, and - either generated output becoming stale; -- the contract does not become a second runtime validator: owner-native - positive/negative admission tests remain required evidence, and the public - contract explicitly denies nested, typed, cardinality, nullability, and - cross-field semantic parity. - -Rejected lower-cost alternative: - -A source-file hash alone detects irrelevant refactors and still does not state -the contract. The first implementation's inferred nested record graph is also -rejected: it attached records by field-name heuristics and produced -demonstrably false scalar-to-record associations. A generic owner string does -not expose compatibility. Merely adding `without` prose to overlapping -conditions is also rejected because free text cannot prove dimension closure, -canonicality, or pairwise disjointness. A general SAT or CLI grammar is -unnecessary: the optional four-dimension conjunction model closes the only -confirmed machine-selection boundary. - -Proof invariant: - -Changing a declared root kind, top-level field, requiredness rule, schema -version, or JSON-producing flag/mode variant changes the public ABI projection -and fails the golden until reviewed. Changing native owner-package source also -forces review without being mislabeled as semantic proof. - -Non-claims: - -The summary is not JSON Schema. It does not describe nested fields, scalar -types, array item types or cardinality, nullability, or cross-field semantics, -and it does not replace command admission tests. - -Rollback or overturn condition: - -A future versioned JSON Schema surface may supersede these closed machine -projections after demonstrating lower duplication and exact parity. - -Why this avoids accidental complexity: - -It uses one bounded root-shape registry and deterministic projection generator -instead of a false full-schema model, two manually authored public/private -schema copies, or a schema service. - -Why this avoids premature over-decomposition: - -The public contract is authored once; generated Go metadata is derived and -freshness-checked. - -### D-07: Pre-1.0 compatibility - -Problem: - -A breaking `0.1.x` patch is selected by a common caret range. - -Chosen owner boundary: - -Release change admission and package-public install documentation jointly own -the policy. - -Design: - -- canonical npm installation uses `--save-exact`; -- README and generated release notes state that pre-1.0 consumers must - exact-pin; generated npm install and rollback commands include - `--save-exact` and the rollback route names the literal admitted previous - version; -- replace `release/change-record.v1.json` with - `release/change-record.v2.json`; update `releasechange.RecordPath`, release - input composers, manifests, closeout, workflow paths, bindings, package - checks, and documentation in the same slice; -- change-record schema v2 gains exact `previousVersion` and closed - `changeClass=compatible|breaking`; -- admission derives that non-empty `breakingChanges` or required migration - requires `changeClass=breaking`; -- release validation rejects non-empty breaking changes or required migration - when the new version is only a patch over the previous pre-1.0 version; -- a future breaking pre-1.0 release must increment the minor version; -- no v1 adapter is added because no durable consumer requires one; the - version-bound record is a repository release input, and retaining two active - paths would create ambiguous authority; -- this change advances all synchronized package, Python, release record, - manifest, notes, and contract metadata from `0.1.160` to `0.2.0` with - `previousVersion=0.1.160`; -- the already published `v0.1.160` is not changed, republished, or backfilled. - -Rejected lower-cost alternative: - -Documentation alone does not prevent a future incompatible patch. A validator -alone does not protect existing range consumers. - -Proof invariant: - -For `0.m.p -> 0.m.(p+1)`, breaking changes or required migration fail release -admission; `0.1.160 -> 0.2.0` is admitted as breaking; install and rollback -release-note projections both preserve exact npm dependency pins. - -Non-claims: - -This does not prove registry publication, downstream lockfile use, or adoption. - -Rollback or overturn condition: - -Only a versioned release policy with an equally strong consumer-compatibility -proof may replace exact pins and minor bumps. - -Why this avoids accidental complexity: - -It adds two version facts and one semver comparison to the existing owner. - -Why this avoids premature over-decomposition: - -No new versioning package is needed unless another owner reuses the algorithm. - -### D-08: Independent blocking falsifiers - -Problem: - -Aggregate positive fixtures allow individual blocking conjuncts to become -inert without failing tests. - -Chosen owner boundary: - -Existing self-hosting, coverage, package verification, and release tests. - -Design: - -- extract pure verdict functions only where subprocess coupling currently - prevents testing; -- mutate one predicate per table row; -- cover self-hosting report state, wheel SHA, version match, local/CI receipt - identity, tarball root deny-list, command-route closure, and linkage - dead-zone fields; -- retain route-only metrics as non-claims where the owner explicitly makes - them non-blocking; -- do not convert line coverage into semantic proof. - -Rejected lower-cost alternative: - -One fixture with every field wrong cannot prove that every conjunct matters. - -Proof invariant: - -Removing or inverting any single blocking predicate fails at least its named -negative case, while the neighboring positive fixture remains green. - -Non-claims: - -These tests do not prove exhaustive input coverage or provider state. - -Rollback or overturn condition: - -A predicate may lose its falsifier only if the durable requirement removes it -from the blocking conjunction. - -Why this avoids accidental complexity: - -Pure helpers are admitted only at existing side-effect boundaries. - -Why this avoids premature over-decomposition: - -No test utility package is added for one-use helpers. - -### D-09: Continuous onboarding - -Problem: - -The shipped path breaks between local install, executable resolution, family -discovery, preset vocabulary, and first valid input. - -Chosen owner boundary: - -README, direct/root help, descriptors, stack preset inventory, package smoke, -and one marker-bounded example. - -Design: - -- npm install uses `--save-exact`; -- commands use `npm exec --offline -- agentic-proofkit`; -- Bun is not presented as a verified canonical onboarding route in this - change; adding it later requires an exact-pin artifact smoke equivalent to - the npm witness; -- root help projects the `help families` route; -- the projected route is the copyable - `npm exec --offline -- agentic-proofkit help families` command used by the - installed consumer, and its witness executes argv parsed from those exact - displayed bytes; -- the authored CLI contract is the sole editable preset-ID owner; - `stackpreset.IDs()` returns a defensive copy of its generated lower-package - projection, while direct help and diagnostics use that API and app metadata - uses the sibling generated projection; -- README includes one minimal valid requirement source and a tested command; -- package verification installs the exact tarball and executes one continuous - offline witness trace through the installed artifact: root help, family - discovery, every stack preset ID, extraction of the marker-bounded example - from the installed README, and successful admission of that example; -- one immutable `cliexec` renderer owns shell quoting and a previously admitted - invocation prefix; it has exactly three profiles: - `npm_offline` renders `npm exec --offline -- agentic-proofkit`, - `python_module` renders the absolute admitted interpreter followed by - `-m agentic_proofkit`, and `path` renders `agentic-proofkit`; -- the npm shell wrapper and Python wrapper overwrite private launcher - environment fields `AGENTIC_PROOFKIT_LAUNCHER_PROFILE` and - `AGENTIC_PROOFKIT_PYTHON_EXECUTABLE` before `exec`; the closed admission - matrix is `("", "")` or `("path", "")` to `path`, - `("npm_offline", "")` to `npm_offline`, and - `("python_module", )` to - `python_module`; every unknown profile, relative or empty Python executable, - secret-like, control-bearing, or format-bearing executable, or executable - field supplied with another profile is rejected without disclosing the - rejected value; the Go process boundary admits those - fields once and passes the renderer explicitly through app and command - builders, with no package-manager, executable, `PATH`, or repository-state - autodetection; -- the exact current Proofkit-owned generated-command field inventory is closed - over: - `$.diagnostics[?key=preset].value.suggestedCommands[*]` for stack-preset; - `$.nextCommands[*]`, - `$.agentActionPlan[?phase=verify].commands[*]`, - `$.payloads.adoptionGuidance.agentGuidance.commands[callerCommandCount:]`, - `$.report.diagnostics[?key=agentActionPlan].value[?phase=verify].commands[*]`, - and `$.report.diagnostics[?key=nextCommands].value[*]` for bootstrap JSON; - `$.commands[*].command` for its agent envelope; - `$.nextCommands[*]` plus decoded - `$.files[?payloadKey=adoptionGuidance].content::$.agentGuidance.commands[callerCommandCount:]` - for its materialization manifest; - the same bootstrap display-command locations below `$.bootstrapReport`, plus - `$.materializationManifest.nextCommands[*]` and decoded - `$.materializationManifest.files[?purpose=caller-owned gradual adoption - guidance input].content::$.agentGuidance.commands[callerCommandCount:]` - for project-structure JSON; and `$.commands[*].command` for the - project-structure agent envelope; adding, removing, or relocating a producer - or field requires the same inventory, requirement, and witness update; -- the structured-argv inventory is separately closed over - `$.nextCommands[*].argv`, agent-envelope `$.commands[*].argv`, and exact - agent-envelope `$.commands[*].display == cliexec.DisplayArgv(argv)` for - agent-route; release-phase `$.phases[?phase=release].commands[*].argv` for a - direct and aggregate adoption workflow; `$.commands[*].argv` plus exact - `command == cliexec.DisplayArgv(argv)` for their agent envelopes; - failure-rerun `$.commands[*].argv` for requirement coverage; and project - `$.adoptionWorkflowPlan.phases[?phase=profile].commands[*].argv`, - `$.adoptionWorkflowPlan.phases[?phase=bootstrap].commands[*].argv`, and - `$.adoptionWorkflowPlan.phases[?phase=bind].commands[*].argv` with exact - counts `2`, `2`, and `3`; project source-report identity is derived from the - same renderer-owned workflow record; -- the textual help inventory is closed over the root family-discovery route, - every family route, every family-to-leaf route, every descriptor's installed - invocation, the help descriptor's exact authored help forms, and every - stack-preset copyable route; path, npm-offline, and Python-module profiles - must render every slot exactly once; -- caller-owned bootstrap `commands` bytes remain unchanged and are proved by - `TestBootstrapPreservesCallerDisplayCommandInGuidancePayload`; specifically, - the prefix - `$.payloads.adoptionGuidance.agentGuidance.commands[0:callerCommandCount]` - and its decoded materialization copies remain caller-owned while only the - suffix is renderer-owned; -- caller-owned native-witness argv below the project bootstrap report remains - byte-for-byte equal to the admitted bootstrap input and is excluded from the - Proofkit-owned structured-argv inventory; -- the installed npm trace parses every preset's exact generated command - strings through the bounded literal-word boundary, requires the exact npm - prefix on every string, and re-executes a self-continuation from those exact - JSON bytes; -- the installed wheel trace invokes a preset through the installed Python - module, requires every emitted command to use its exact venv interpreter and - `-m agentic_proofkit`, directly re-executes a self-continuation, traverses - root help through every family and leaf help route, emits an agent-route argv - with the same immutable prefix, and directly executes that argv with npm - absent from `PATH`; route extraction admits only exact authored four-space - indentation and one canonical lower-case command operand, rejects whitespace - and shell-expansion mutants, and never turns generated stdout into shell - authority; -- Python docs state `python -m agentic_proofkit` and `uv run - agentic-proofkit`, supported targets, Python minimum, wrapper-not-SDK, and - explicit registry-availability non-claims; -- a marker-bounded platform block is projected exactly from - `releaseplatform.Targets()`, macOS minimum 12, manylinux 2.17 arm64/x64, - Python `>=3.9`, and explicit Windows non-support; a docs test compares every - row with the private owners; -- Python examples include the complete conditional install-to-invoke chain: - `python -m pip install agentic-proofkit==` then - `python -m agentic_proofkit`, and - `uv add --dev agentic-proofkit==` then - `uv run agentic-proofkit`, without implying that a current registry version - exists; -- remove contributor-only `AGENTS.md` and `CONTRIBUTING.md` from the npm - package, remove the active-backlog route from package-public README, and add - a field-aware package-reference-closure falsifier; -- update or exclude package projections such as - `receipt-producer-policy.local.developer` that cite contributor-only files, - and classify self-hosting witness selectors that name `AGENTS.md`, - `BACKLOG.md`, or `CONTRIBUTING.md` as source-checkout-only rather than - package-consumer routes; -- `BACKLOG.md` remains a source-checkout owner and is not shipped as - version-specific consumer documentation. - -Exact C-89 proof routes: - -| Requirement | Scenario | Witness path | Selector | Executable command | -| --- | --- | --- | --- | --- | -| `REQ-PROOFKIT-PACKAGE-002` | `proofkit.package-boundary.launcher-profile-admission` | `internal/kernel/cliexec/cliexec_test.go` | `TestLauncherProfileAdmissionMatrix` | `go test ./internal/kernel/cliexec -run '^TestLauncherProfileAdmissionMatrix$'` | -| `REQ-PROOFKIT-PACKAGE-002` | `proofkit.package-boundary.generated-command-field-inventory` | `internal/app/invocation_profile_test.go` | `TestGeneratedCommandInvocationProfileFieldInventory` | `go test ./internal/app -run '^TestGeneratedCommandInvocationProfileFieldInventory$'` | -| `REQ-PROOFKIT-PACKAGE-002` | `proofkit.package-boundary.generated-command-field-inventory` | `internal/app/invocation_profile_test.go` | `TestGeneratedCommandInvocationProfileRouteClosure` | `go test ./internal/app -run '^TestGeneratedCommandInvocationProfileRouteClosure$'` | -| `REQ-PROOFKIT-PACKAGE-002` | `proofkit.package-boundary.generated-command-caller-preservation` | `internal/command/gradualadoption/gradualadoption_test.go` | `TestBootstrapPreservesCallerDisplayCommandInGuidancePayload` | `go test ./internal/command/gradualadoption -run '^TestBootstrapPreservesCallerDisplayCommandInGuidancePayload$'` | -| `REQ-PROOFKIT-PACKAGE-003` | `proofkit.package-boundary.outside-consumer-artifact` | `internal/tools/packageverify/main_test.go` | `TestExactTarballOnboardingTrace` | `go test ./internal/tools/packageverify -run '^TestExactTarballOnboardingTrace$'` | -| `REQ-PROOFKIT-PACKAGE-006` | `proofkit.package-boundary.python-wheel-generated-continuation` | `internal/tools/pythonpackage/continuation_test.go` | `TestInstalledWheelContinuationUsesExactPythonModuleProfileWithoutNPM` | `go test ./internal/tools/pythonpackage -run '^TestInstalledWheelContinuationUsesExactPythonModuleProfileWithoutNPM$'` | -| `REQ-PROOFKIT-PACKAGE-006` | `proofkit.package-boundary.python-wheel-generated-continuation` | `internal/tools/pythonpackage/continuation_test.go` | `TestExactDisplayedRouteOperandsRejectsWhitespaceAndExpansionMutants` | `go test ./internal/tools/pythonpackage -run '^TestExactDisplayedRouteOperandsRejectsWhitespaceAndExpansionMutants$'` | -| `REQ-PROOFKIT-QUALITY-019` | `proofkit.supply-chain-quality.installed-package-json-abi-smoke` | `internal/tools/packageverify/main_test.go` | `TestExactTarballOnboardingTrace` | `go test ./internal/tools/packageverify -run '^TestExactTarballOnboardingTrace$'` | -| `REQ-PROOFKIT-QUALITY-024` | `proofkit.supply-chain-quality.release-change-record-projection` | `internal/tools/releasechange/record_test.go` | `TestCurrentChangeRecordNamesReviewedSemanticChanges` | `go test ./internal/tools/releasechange -run '^TestCurrentChangeRecordNamesReviewedSemanticChanges$'` | - -The coverage owner admits these rows as an exact critical inventory and rejects -empty, missing, surplus, selector substitution, witness relocation, executable -command drift, requirement transfer, and scenario transfer. - -Rejected lower-cost alternative: - -Global installation, bare `npx`, or network fallback changes package identity. -Printing all allowed keys on every malformed input is noisy and does not create -a successful first route. - -Proof invariant: - -For the installed `npm_offline` and `python_module` channels and every field in -the closed generated-command inventory, the emitted command resolves the same -candidate artifact without network fallback; both temporary consumers can -execute an exact emitted self-continuation. The `path` profile preserves only -the canonical bare executable token and caller-owned resolution, without an -artifact-identity claim. Caller-owned display bytes are preserved in every -profile. - -Non-claims: - -The docs do not prove npm or PyPI publication, Bun support execution, shell -portability outside supported package targets, or consumer adoption. A direct -binary uses the `path` profile and therefore still requires the caller to make -`agentic-proofkit` resolvable. - -Rollback or overturn condition: - -If a documented package channel is removed from durable release owners, its -route and test must be removed together. - -Why this avoids accidental complexity: - -It projects existing owners and adds no command. - -Why this avoids premature over-decomposition: - -The only shared field is descriptor flag-value choices. - -### D-10: Browser state and narrow accessibility proof - -Problem: - -Initial markup, terminal failures, 320-pixel layout, controls, and axe coverage -do not satisfy a coherent stable-state contract. - -Chosen owner boundary: - -Existing workspace HTML/assets and Playwright witness. - -Design: - -- server HTML contains an initial loading status; -- initialization catches manifest failure and renders a sanitized alert; -- request failures use the same terminal state vocabulary; -- native list and article semantics replace the unjustified ARIA tree; -- handoff output uses a visible heading and labelled region; -- active view controls expose `aria-current`; -- grid children use `min-width: 0`, text can wrap, navigation wraps, and - graph/table overflow is confined to labelled internal viewports; -- explicit light/dark control tokens preserve forced-colors adaptation; -- axe runs on bootstrap loading, bootstrap failure, specifications loading, - specifications, specifications no-match, diff loading, diff, graph loading, - graph, unavailable, failed, and handoff-result states; -- the Playwright matrix declares for each state its deterministic route - interception or deferred-response barrier, exact body and content - `data-state` where applicable, heading, and applicable axe/reflow checks; the - observed state identity is asserted before every oracle; -- bootstrap loading is held by a deferred manifest response, and each - specifications, diff, and graph loading state is independently held by its - own deferred view response; every barrier is released only after its complete - row oracle, and bootstrap, view, and handoff failures are separate rows; -- `target-size` is explicitly enabled, applies to representative controls, - has zero violations, and an undersized-control negative fixture proves that - the rule would fail; -- a 320 by 800 viewport asserts no document-level horizontal overflow after - each view transition; -- computed contrast checks read actual rendered controls, adjacent - backgrounds, border colors, opacity, and focused outline styles in pinned - engines and light/dark schemes rather than merely checking token values; -- replacing the synthetic tree intentionally removes its ArrowUp/ArrowDown - roving-focus contract; tests preserve standard Tab/Shift+Tab traversal, - Enter/Space activation, selection, and handoff semantics. - -Rejected lower-cost alternative: - -`overflow-x: hidden` hides data. Keeping a synthetic tree adds an unsupported -keyboard contract. A single final-state axe run does not prove initial or -failure states. - -Proof invariant: - -Every stable state is non-empty, has no default axe violation, executes the -target-size rule with no violation, and reflows without document overflow -where applicable. - -Non-claims: - -The witness does not establish complete WCAG conformance, branded Safari -behavior, screen-reader interoperability, all OS themes, or 400-percent zoom. - -Rollback or overturn condition: - -New stable UI states must enter the state matrix or be explicitly classified -as transient and inaccessible to users. - -Why this avoids accidental complexity: - -It removes an ARIA widget and centralizes one test helper. - -Why this avoids premature over-decomposition: - -CSS and rendering remain in the existing asset owner. - -### D-11: Honest digest-coverage naming - -Problem: - -`baselineVerification=verified` sounds like provenance even though it means -only that all caller-provided expected digests match current bytes. - -Chosen owner boundary: - -Versioned requirement-context snapshots and downstream diff/browser -projections. - -Design: - -- snapshot schema v2 emits `expectedDigestCoverage: none|partial|all`; -- v1 input is first fully admitted under the complete v1 contract, then - normalized by an explicit legacy adapter: - `unverified -> none`, `partially_verified -> partial`, `verified -> all`; -- all producers emit v2; -- semantic-diff input/output, workspace manifest, and affected HTTP - projections advance to their own schema v2 envelopes and use the new name; -- each affected v1 envelope has a strict v1 adapter, mixed v1/v2 keys are - rejected, and migration tests cover v1 admission, normalized v2 equality, - v2 production, and stable rejection of malformed legacy data; -- UI says `Expected-digest coverage`, never `Baseline verified`; -- requirements and non-claims state that coverage does not authenticate a - producer, baseline, checkout, or freshness. - -Rejected lower-cost alternative: - -A producer marker inside caller-computable data creates no provenance. -Changing UI text alone leaves the wire contract misleading. - -Proof invariant: - -Self-consistent caller data remains admissible. The legacy verification term -may appear only inside the strict v1 adapter, migration fixtures, and -compatibility diagnostics; no v2 output, current contract, direct help, or UI -calls digest coverage verified. - -Non-claims: - -No signatures, trusted producer, repository freshness, or merge authority are -added. - -Rollback or overturn condition: - -A future authenticated snapshot format may introduce a separate provenance -field with its own trust root; it must not reuse digest coverage. - -Why this avoids accidental complexity: - -One versioned rename removes a false semantic implication. - -Why this avoids premature over-decomposition: - -The legacy adapter stays in the snapshot model owner. - -### D-12: Semantic Markdown phrase equivalence - -Problem: - -Readiness overclaim scanning compares source bytes that can contain one -semicolon-terminated CommonMark character reference equivalent to a direct -policy phrase. - -Chosen owner boundary: - -The readiness command keeps structural table parsing and phrase policy local. - -Design: - -- parse rows and cells from original Markdown bytes; -- decode exactly one strict semicolon-terminated named, decimal, or hexadecimal - CommonMark character reference only in extracted textual segments before - phrase normalization; -- use a bounded recognizer around the standard entity table rather than - applying permissive HTML decoding to arbitrary ampersand text; -- normalize policy phrases through the same helper; -- do not decode before pipe parsing; -- do not recursively decode double-encoded values. - -Rejected lower-cost alternative: - -Rejecting every ampersand breaks legitimate Markdown and still does not state -visible-text semantics. - -Proof invariant: - -Semicolon-terminated named, decimal, and hexadecimal references classify -identically to direct text; missing-semicolon and double-encoded references -remain literal after one pass. - -Non-claims: - -This is not a full Markdown parser or extraction-completeness proof. - -Rollback or overturn condition: - -A full admitted Markdown AST may supersede the helper only with equivalence -falsifiers. - -Why this avoids accidental complexity: - -One standard-library decode at the semantic boundary is sufficient. - -Why this avoids premature over-decomposition: - -The helper remains command-local until reused by another policy owner. - -## Documentation topology - -The closed size audit uses a deterministic suspicion threshold: -`LOC >= 1000 or bytes >= 65536`. Crossing the threshold is necessary only for -this ledger, not sufficient for a god-file verdict. A proven god file also -requires at least two independent semantic owners and observed or reproducible -material harm from their concentration. - -Candidate snapshot ledger: - -| Path | LOC | Bytes | Disposition | Owner proof | -|---|---:|---:|---|---| -| `proofkit/cli-contract.v2.json` | 14,983 | 537,788 | Suspicious size; not god | One generated public CLI-contract projection with freshness and ABI oracles | -| `docs/implementation/audit-remediation-plan.md` | 4,077 | 187,518 | Temporary oversized execution document | One reviewed implementation graph; retirement is required by the closeout predicate | -| `proofkit/requirement-bindings.json` | 3,507 | 142,191 | Suspicious size; not god | One canonical binding registry whose global order and linkage closure require one record | -| `scripts/workflow_package_gate_oracle_test.go` | 2,606 | 80,168 | Remediated god-file; residual suspicious cluster | Five-owner form was split; remaining QUALITY-011/013 scenarios share an exact selector and single-path binding contract | -| `internal/tools/packageverify/main.go` | 2,590 | 92,501 | Suspicious size; not proven god | One npm artifact admission boundary; helper extraction requires a second durable consumer or independent change reason | -| `docs/implementation/audit-remediation-design.md` | 3,182 | 248,568 | Temporary oversized design document | One adjudicated correction ledger; retirement is required after durable-owner closeout | -| `internal/app/cli_abi_test.go` | 2,316 | 112,885 | Suspicious size; not god | One public CLI ABI corpus and golden identity | -| `internal/app/cli_contract_test.go` | 2,096 | 87,274 | Suspicious size; not god | One CLI contract-admission and native-source parity corpus | -| `internal/tools/releasecloseoutinput/main_test.go` | 1,757 | 72,617 | Suspicious size; not god | One release-closeout input anti-corruption boundary | -| `internal/tools/packageverify/main_test.go` | 1,752 | 64,724 | Suspicious size; not god | One npm artifact verifier corpus | -| `internal/command/agentroute/agentroute_test.go` | 1,683 | 55,743 | Suspicious size; not god | One command owner and its complete behavioral corpus | -| `internal/tools/releasecloseoutinput/main.go` | 1,674 | 63,256 | Suspicious size; not god | One closeout projection owner | -| `proofkit/witness-plan.json` | 1,440 | 37,233 | Suspicious size; not god | One generated global witness plan | -| `internal/command/testevidenceinventory/testevidenceinventory_test.go` | 1,310 | 52,773 | Suspicious size; not god | One test-evidence inventory command corpus | -| `internal/command/repoprofileadmission/repo_profile_admission.go` | 1,280 | 42,499 | Suspicious size; not god | One repository-profile admission state machine | -| `internal/command/requirementcoverageview/requirementcoverageview_test.go` | 1,276 | 54,939 | Suspicious size; not god | One requirement-coverage view corpus | -| `internal/app/app_test.go` | 1,266 | 50,345 | Suspicious size; not god | One top-level command dispatcher and process-channel corpus | -| `internal/command/requirementbinding/requirementbinding.go` | 1,155 | 37,562 | Suspicious size; not god | One requirement-binding admission owner | -| `internal/command/pilotadmission/pilotadmission.go` | 1,154 | 43,660 | Suspicious size; not god | One pilot-admission command owner | -| `internal/command/agentroute/agentroute.go` | 1,121 | 51,789 | Suspicious size; not god | One agent-route command owner | -| `tests/browser/workspace.spec.mjs` | 1,416 | 60,370 | Suspicious size; not proven god | One end-to-end browser contract corpus; helper extraction would split shared state and add a one-consumer abstraction | -| `internal/command/workspaceregistry/workspaceregistry.go` | 1,096 | 34,838 | Suspicious size; not god | One workspace-registry command owner | -| `internal/command/bindingpartition/bindingpartition.go` | 1,086 | 39,825 | Suspicious size; not god | One binding-partition command owner | -| `internal/command/releaseauthority/releaseauthority.go` | 1,083 | 39,167 | Suspicious size; not god | One release-authority command owner | -| `.github/workflows/release.yml` | 1,069 | 49,849 | Suspicious size; not god | One event/needs release state machine; splitting jobs into reusable workflows would change trust and permission semantics | -| `internal/command/testevidenceinventory/testevidenceinventory.go` | 1,033 | 35,981 | Suspicious size; not god | One test-evidence inventory command owner | -| `internal/command/capabilitymapadmission/capability_map_admission.go` | 1,015 | 34,953 | Suspicious size; not god | One capability-map admission command owner | -| `internal/command/readinesscloseout/readinesscloseout.go` | 1,014 | 33,435 | Suspicious size; not god | One readiness-closeout command owner | -| `internal/command/jsonreportcliadaptersource/json_report_cli_adapter_source.go` | 1,008 | 34,655 | Suspicious size; not god | One JSON-report CLI-adapter source owner | -| `internal/tools/releasemanifest/main.go` | 1,006 | 36,543 | Suspicious size; not god | One release-manifest construction boundary | -| `internal/command/conformanceprofile/conformanceprofile.go` | 1,000 | 35,766 | Suspicious size; not god | One conformance-profile command owner | - -C-60 decomposes the confirmed concentration as follows: - -- `workflow_package_gate_oracle_test.go` retains the inseparable - QUALITY-011/013 merge/package-gate proof cluster; -- `workflow_oracle_support_test.go` contains shared typed YAML and neutral - helpers and contains no `Test*` selector; -- browser runtime, runtime preconditions, workflow source policy, and security - scanner policy each have their own test file; -- PACKAGE-005, QUALITY-022, and QUALITY-025 bindings point to the new semantic - owners and exact selector inventories reject deletion, surplus, - owner-transfer, or stale-path substitution. -- QUALITY-005, QUALITY-006, and QUALITY-007 bind the scanner-policy selectors - to their extracted owner, and the same exact selector-and-path inventory - rejects deletion, surplus, owner transfer, or relocation. - -The reverse decomposition audit found no proven merge. It inspected every Go -file at or below 40 LOC and every package with at least eight Go files. -The strongest candidates were rejected for explicit boundaries: - -- `requirementbrowser/v1_adapter.go` owns a retireable wire-version adapter; -- generated preset IDs are generator-owned output in a different Go package; -- `cmd/agentic-proofkit/main.go` is the executable boundary; -- browser `assets.go` is the embed boundary; -- small app command wrappers preserve command-route and native-source review - identities, while merging them would not remove a dependency or duplicate - algorithm. - -Reopen a split or merge only when owner evidence proves an independent change -reason, duplicated algorithm, dependency-cycle reduction, or measurable review -harm. File count, line count, and aesthetic preference alone are insufficient. -New helpers remain admissible only when they isolate a pure predicate, project -one private owner, bind a confined handle, or serve the shared workflow -anti-corruption boundary demonstrated above. - -Implementation-only documents stay outside `package.json.files`. Contributor -governance and `BACKLOG.md` also remain source-checkout surfaces: an installed -runtime dependency must not expose incomplete repository-governance routes or -version-specific work rows. - -### D-13: Immutable release topology and exact browser side effects - -Problem: - -The release workflow can add missing assets to an existing GitHub Release even -though the release owner calls historical release evidence immutable. The -browser owner separately uses an over-broad prohibition that appears to ban its -own fixed `--open` launcher, and one renderer diagnostic omits `workspace`. - -Chosen owner boundary: - -The release workflow may validate but never mutate an existing release. The -browser server may invoke only a fixed platform launcher with its own admitted -loopback URL. View vocabulary remains local to the browser command and app -parity tests. - -Design: - -- remove the existing-release missing-asset upload branch; -- existing releases must have the exact expected asset names and bytes or fail - with a terminal nonzero result before any provider mutation; -- admit a closed read-only provider command set for the existing-release path - (`gh release view` and `gh release download` in exact owner-approved forms); -- compare the entire existing-release shell block to one canonical - owner-reviewed source form, including every local and provider operation; - any `curl`, alternate network client, shell indirection, additional command, - or other source change fails the oracle until a new owner-reviewed form and - negative case are admitted; -- historical exceptions may be recorded outside successful release evidence - but never authorize upload or a passing release result; -- refine `SPEC-021` to prohibit caller-supplied and native-witness command - execution while permitting the fixed browser launcher; -- inject the launcher operation for tests and require fixed executable/argv - forms plus an admitted loopback URL; -- update the renderer diagnostic to include `workspace` and add app/runtime - vocabulary parity. - -Rejected lower-cost alternative: - -Add-only backfill still mutates historical evidence topology. Removing -`--open` breaks the admitted one-shot workflow. A new shared enum package for -two local projections is premature. - -Proof invariant: - -An incomplete existing release terminates nonzero before any provider -mutation, and no caller value can select the browser executable or add launcher -arguments. - -Non-claims: - -Source tests do not prove provider immutability, actual release assets, OS -browser profile identity, or that the browser rendered successfully. - -Rollback or overturn condition: - -Only a versioned release owner may define a mutable evidence class. A future -launcher expansion requires a new trust-boundary contract. - -Why this avoids accidental complexity: - -It deletes a mutation path and adds narrow injected-operation tests. - -Why this avoids premature over-decomposition: - -Release and browser vocabularies remain with their current owners. - -## Compatibility and business-logic proof - -Expected intentional public changes: - -- blocked prerequisites can no longer return a successful adoption report; -- readiness closeout decodes one strict semicolon-terminated CommonMark or HTML - character reference pass before policy phrase matching, so a forbidden - phrase hidden by one such reference now fails closed; -- malformed input for the nine affected commands moves from JSON `stdout` to - diagnostic `stderr`; -- CLI contract compatibility projection expands; -- `pilot-admission --pilot all` contract envelopes admit one strict first and - stack-diverse input pair and return those ordered pilot reports; -- requirement binding admission and output preserve optional - `witnessSelectors` selector-and-command records; -- requirement-context, semantic-diff, workspace manifest, and affected HTTP - projection outputs advance to v2 with strict v1 input adapters; -- package/release metadata advances from `0.1.160` to `0.2.0`; -- future breaking pre-1.0 patch releases are rejected; -- absolute TypeScript source/package symlink targets are rejected; relative - in-root symlinks remain supported; -- help and docs expose existing routes and preset values; -- synthetic ArrowUp/ArrowDown tree focus behavior is removed with the - unjustified ARIA tree; standard Tab/Shift+Tab and Enter/Space behavior is - preserved. - -Preserved behavior: - -- valid admitted inputs retain the same semantic decisions except for the - explicitly declared readiness-closeout normalization and migrations above; -- semantic failed reports remain JSON reports; -- observe/warn modes remain advisory for non-blocked gaps; -- stack preset IDs and profiles do not change; -- output bytes and mode on a stable safe path remain deterministic; -- scanner export grammar and comparison do not expand; -- browser projections remain presentation-only; -- package artifacts remain candidate evidence until provider and registry proof - exists. - -For every intentional public change, the change record, contract projection, -requirements, bindings, and migration text must agree before closeout. - -## Durable proof routing - -Every row below is an implementation obligation. Proposed new scenario and -witness IDs become exact binding identities in -`proofkit/requirement-bindings.json`; existing identities are retained where -they already own the boundary. Test selectors are fixed before production -edits. - -| Finding | Exact requirements | Binding scenario and witness | Exact path, selector, and command | Required non-claim delta | -|---|---|---|---|---| -| R-01 | `REQ-PROOFKIT-RETIRE-008`, `REQ-PROOFKIT-QUALITY-024` | extend `proofkit.consumer-infra-retirement.adoption-doctor-enforcement` / `proofkit.adoption-doctor.enforcement-and-envelope` and `proofkit.supply-chain-quality.release-change-record-projection` / `proofkit.release-change.versioned-projection-falsifier` | `internal/command/adoptiondoctor/adoptiondoctor_test.go`, `TestBuildReportsObserveAndWarnWithoutBlockingAdvisoryGaps`, `TestBuildEnforceTouchedSkipsGapsOutsideTouchedSelection`, `TestBuildBlocksEveryModeForExternalPreconditions`; `internal/tools/releasechange/record_test.go`, `TestCurrentChangeRecordNamesReviewedSemanticChanges`; `go test ./internal/command/adoptiondoctor ./internal/tools/releasechange` | Advisory mode does not authenticate or satisfy blocked evidence; a local record does not prove consumer migration | -| R-02 | `REQ-PROOFKIT-QUALITY-002` | extend `proofkit.supply-chain-quality.release-sbom` / `proofkit.release-sbom.deterministic-inventory` | `internal/tools/releasesbom/main_test.go`, `TestArtifactSpecificRuntimeEdgesAndExcludedInventory`, `TestReleaseFileEvidenceRejectsDeterministicIdentitySwap`, `TestReleaseFileEvidenceRejectsDeterministicInPlaceMutation`, `go test ./internal/tools/releasesbom` | Required scope and edges do not prove reachability, vulnerability absence, or license approval | -| R-03 | `REQ-PROOFKIT-PACKAGE-002` | extend `proofkit.package-boundary.typescript-explicit-scan-topology` / `proofkit.typescript-public-api.explicit-scan-topology` | `internal/command/publicapi/public_api_test.go`, `TestScanCacheBindsBytesToFirstCanonicalIdentityAcrossSymlinkRetarget`, `TestCanonicalSourceSnapshotRejectsChangedCrossAliasAdmission`, `TestVerifyRejectsDeterministicSymlinkSwap`, `TestVerifyPinsPackageRootAcrossInRootSiblingSwap`, `go test ./internal/command/publicapi` | Confined read does not prove checkout freshness or compiler provenance | -| R-04 | `REQ-PROOFKIT-SPEC-009` | extend `proofkit.spec-proof-core.requirement-spec-tree-view-cli-output` / `proofkit.requirement-spec-tree-view.cli-output-path-falsifier` | `internal/app/cli_abi_test.go`, `TestOutputWriterRejectsDeterministicParentSwap`, `go test ./internal/app -run OutputWriter` | Confined atomic rename does not prove protection from same-user content or namespace mutation during the operation or fsync durability | -| R-05 | `REQ-PROOFKIT-QUALITY-013` | extend `proofkit.supply-chain-quality.workflow-package-gate-oracle` / `proofkit.workflow-package-gate.typed-oracle` | `scripts/workflow_package_gate_oracle_test.go`, `TestCIWorkflowDeclaresFailClosedRequiredAggregate`, `TestPackageGateWorkflowOracleAcceptsOwnerCIAndReleaseWorkflows`, `TestWorkflowGuardExpressionsRejectNeutralization`, `TestPackageGateWorkflowOracleRejectsDisabledAndShadowedEvidence`, `TestPackageGateWorkflowOracleRejectsExecutionOverrides`, `TestPackageGateWorkflowOracleRejectsRequiredPriorExecutionOverride`, `TestPackageGateWorkflowOracleRejectsUnusedAllowedStepEnvironment`, `go test ./scripts -run 'CIWorkflowDeclaresFailClosedRequiredAggregate|WorkflowGuard|PackageGateWorkflowOracle'` | Exact source forms do not prove provider execution | -| R-06 | `REQ-PROOFKIT-QUALITY-011` | extend `proofkit.supply-chain-quality.ci-required-aggregate-exactness` / `proofkit.ci.required-aggregate-neutralization-falsifier` | `scripts/workflow_package_gate_oracle_test.go`, `TestCIWorkflowDeclaresFailClosedRequiredAggregate`, `TestCIRequiredAggregateRejectsNeutralizedScript`, `TestCIRequiredAggregateRejectsExecutionOverrides`, `TestCIRequiredAggregateRejectsPlatformSmokeSubstitution`, `go test ./scripts -run 'CIWorkflowDeclaresFailClosedRequiredAggregate|CIRequiredAggregate'` | Local source admission does not prove branch protection | -| R-07 | `REQ-PROOFKIT-QUALITY-004` | extend `proofkit.supply-chain-quality.cli-abi-golden` / `proofkit.cli-abi.golden-corpus` | `internal/app/cli_abi_test.go`, `TestRequiredInputCommandsRouteStructuralErrorsByMode`, `go test ./internal/app -run RequiredInputCommandsRoute` | Diagnostics outside the declared corpus are not byte-stable | -| R-08 | `REQ-PROOFKIT-PACKAGE-002`, `REQ-PROOFKIT-QUALITY-024` | extend `proofkit.package-boundary.readiness-closeout-overclaim-grammar` / `proofkit.readiness-closeout.overclaim-grammar` and `proofkit.supply-chain-quality.release-change-record-projection` / `proofkit.release-change.versioned-projection-falsifier` | `internal/command/readinesscloseout/readinesscloseout_test.go`, `TestPhraseScanDecodesOneStrictCharacterReference`; `internal/tools/releasechange/record_test.go`, `TestCurrentChangeRecordNamesReviewedSemanticChanges`; `go test ./internal/command/readinesscloseout ./internal/tools/releasechange` | The bounded decoder is not a complete Markdown AST; a local change record does not prove consumer migration | -| R-09 | `REQ-PROOFKIT-QUALITY-004`, `REQ-PROOFKIT-PACKAGE-002` | extend `proofkit.supply-chain-quality.cli-contract-topology` / `proofkit.cli-contract.descriptor-parity` | `internal/app/cli_contract_test.go`, `TestCommandDescriptorContractParityRejectsMutations`; `internal/tools/commandcontractgen/main_test.go`, `TestRenderRejectsIncompleteAndStaleCommandContracts`; `npm run command-contract:check`; `go test ./internal/tools/commandcontractgen ./internal/app -run 'CommandDescriptorContractParity|RenderRejectsIncomplete'` | Machine declaration plus native witnesses does not prove every cross-field semantic constraint | -| R-10 | `REQ-PROOFKIT-QUALITY-024` | extend `proofkit.supply-chain-quality.release-change-record-projection` / `proofkit.release-change.versioned-projection-falsifier` | `internal/tools/releasechange/record_test.go`, `TestAdmitEnforcesVersionedChangeClass`, `go test ./internal/tools/releasechange` | Version admission does not prove registry history | -| R-11a | `REQ-PROOFKIT-PACKAGE-004` | add `proofkit.package-boundary.self-hosting-report-verdict` / `proofkit.self-hosting.report-verdict-falsifier` | `scripts/validate-self-hosting-receipts_test.go`, `TestRunProofkitVerdictCases`, `go test ./scripts -run RunProofkit` | Injected operation tests do not prove provider producer identity | -| R-11b | `REQ-PROOFKIT-PACKAGE-006`, `REQ-PROOFKIT-QUALITY-023` | extend `proofkit.package-boundary.python-wheel-candidate` / `proofkit.python-package.boundary` | `scripts/validate-self-hosting-receipts_test.go`, `TestPythonArtifactRefsRejectEachWheelIdentityDefect`, `go test ./scripts -run PythonArtifactRefs` | Local wheel bytes do not prove PyPI bytes | -| R-11c | `REQ-PROOFKIT-PACKAGE-001` | extend `proofkit.package-boundary.root-export-and-deep-import-denial` / `proofkit.package-artifact.boundary` | `internal/tools/packageverify/main_test.go`, `TestVerifyRootPackageRejectsEachForbiddenRootEntry`, `go test ./internal/tools/packageverify` | Local tarball proof does not prove registry tarball identity | -| R-11d | `REQ-PROOFKIT-PACKAGE-004` | extend `proofkit.package-boundary.ci-receipt-anchor` / `proofkit.ci.receipt-anchor` | `scripts/validate-self-hosting-receipts_test.go`, `TestReceiptIDKeepsLocalAndCIIdentitiesDistinct`, `go test ./scripts -run ReceiptID` | Receipt naming does not authenticate a producer | -| R-12 | `REQ-PROOFKIT-QUALITY-010` | extend `proofkit.supply-chain-quality.coverage-metrics` / `proofkit.coverage-metrics.linkage-report` | `internal/tools/coveragemetrics/main_test.go`, `TestEachCommandRouteClosureConjunctHasIndependentFalsifier`, `TestEachLinkageDeadZoneConjunctHasIndependentFalsifier`, `go test ./internal/tools/coveragemetrics` | Static route closure does not satisfy semantic falsifier coverage | -| R-13 | `REQ-PROOFKIT-QUALITY-025` | extend `proofkit.supply-chain-quality.workflow-source-oracles` / `proofkit.workflow.exact-source-oracle-falsifiers` | `scripts/workflow_source_oracles_test.go`, `TestWorkflowExternalActionsUseFullCommitSHAs`, `go test ./scripts -run ExternalActions` | A commit pin does not prove action safety or tag equivalence | -| R-14 | `REQ-PROOFKIT-PACKAGE-003`, `REQ-PROOFKIT-QUALITY-019` | extend `proofkit.package-boundary.outside-consumer-artifact` / `proofkit.package-artifact.outside-consumer` | `internal/tools/packageverify/main_test.go`, `TestExactTarballOnboardingTrace`, `go test ./internal/tools/packageverify -run OnboardingTrace` | Local artifact execution does not prove registry publication | -| R-15 | `REQ-PROOFKIT-SPEC-018` | extend `proofkit.spec-proof-core.command-family-help-compatibility` / `proofkit.command-family-navigation.help-compatibility-falsifier` | `internal/app/command_family_catalog_test.go`, `TestStackPresetVocabularyProjectsFromOneOwner`, `go test ./internal/app -run StackPresetVocabulary` | Presets remain suggestions, not consumer policy | -| R-16 | `REQ-PROOFKIT-SPEC-001`, `REQ-PROOFKIT-QUALITY-019` | add `proofkit.spec-proof-core.installed-readme-first-input` / `proofkit.packageverify.installed-readme-input-falsifier` | `internal/tools/packageverify/main_test.go`, `TestExactTarballOnboardingTrace`, `go test ./internal/tools/packageverify -run OnboardingTrace` | Example validity does not prove requirement meaning | -| R-17 | `REQ-PROOFKIT-PACKAGE-001` | add `proofkit.package-boundary.contract-map-table-shape` / `proofkit.contract-map.table-shape-falsifier` | `internal/app/cli_contract_test.go`, `TestContractMapDecisionTreeHasThreeCells`, `go test ./internal/app -run ContractMap` | Cell-count proof is not full GFM rendering | -| R-18 | `REQ-PROOFKIT-QUALITY-022`, `REQ-PROOFKIT-SPEC-021` | extend `proofkit.supply-chain-quality.browser-static-and-runtime-proof` / `proofkit.requirement-browser.static-runtime-proof-falsifier` | `tests/browser/workspace.spec.mjs`, `workspace state matrix passes axe and target-size`, `npm run browser:check` | Narrow automated rules do not establish full WCAG conformance | -| R-19 | `REQ-PROOFKIT-SPEC-021` | extend `proofkit.spec-proof-core.requirement-browser-rendered-runtime` / `proofkit.requirement-browser.rendered-runtime-falsifier` | `tests/browser/workspace.spec.mjs`, `specifications use native semantics and keyboard activation`, `npm run browser:check` | The flat list does not claim hierarchical tree navigation | -| R-20 | `REQ-PROOFKIT-QUALITY-022` | extend `proofkit.supply-chain-quality.browser-static-and-runtime-proof` / `proofkit.requirement-browser.static-runtime-proof-falsifier` | `tests/browser/workspace.spec.mjs`, `workspace states reflow at 320 CSS pixels`, `npm run browser:check` | The test is not a complete zoom/device audit | -| R-21 | `REQ-PROOFKIT-QUALITY-022` | extend `proofkit.supply-chain-quality.browser-static-and-runtime-proof` / `proofkit.requirement-browser.static-runtime-proof-falsifier` | `tests/browser/workspace.spec.mjs`, `rendered controls meet narrow contrast thresholds`, `npm run browser:check` | Pinned engine/scheme checks do not cover every OS theme | -| R-22 | `REQ-PROOFKIT-SPEC-018` | extend `proofkit.spec-proof-core.command-family-help-compatibility` / `proofkit.command-family-navigation.help-compatibility-falsifier` | `internal/app/command_family_catalog_test.go`, `TestRootHelpDiscoversFamiliesWithoutExpandingThem`, `go test ./internal/app -run CommandFamily` | Root help does not recommend a product decision | -| R-23 | `REQ-PROOFKIT-SPEC-021`, `REQ-PROOFKIT-QUALITY-022` | extend workspace rendered-runtime and static-runtime witnesses | `tests/browser/workspace.spec.mjs`, `bootstrap and request failures are visible and sanitized`, `npm run browser:check` | No retry, telemetry, or offline policy is added | -| R-24 | `REQ-PROOFKIT-SPEC-019`, `REQ-PROOFKIT-SPEC-021`, `REQ-PROOFKIT-SPEC-022`, `REQ-PROOFKIT-SPEC-023`, `REQ-PROOFKIT-QUALITY-004` | extend context compose, semantic diff, graph consumer, browser workspace, and CLI schema-evolution scenarios | context, diff, graph, browser, and app test files; selectors `TestV1DigestCoverageAdapters`, `TestDigestCoverageAdaptersPreserveSemanticDiffV2`, `TestBuildConsumesNormalizedV1AndV2ContextSnapshots`, `TestV2DigestCoverageProjections`, and `TestLegacyDigestVocabularyConfinedToV1AdaptersAndFixtures`; `go test ./internal/command/requirementcontext ./internal/command/requirementdiff ./internal/command/requirementgraph ./internal/command/requirementbrowser ./internal/app` | Digest coverage does not authenticate producer, baseline, or freshness | -| R-25 | `REQ-PROOFKIT-PACKAGE-006`, `REQ-PROOFKIT-QUALITY-016`, `REQ-PROOFKIT-QUALITY-023` | extend Python wheel and platform scenarios / platform-doc and release-platform-parity falsifiers | `internal/tools/pythonpackage/metadata_test.go`, `TestREADMEPlatformAndPythonProjection`, `TestReleaseTargetsProjectExactPythonWheelMetadata`, `TestVerifyWheelContentsRequiresExactWheelMetadata`, `go test ./internal/tools/pythonpackage ./internal/kernel/releaseplatform` | Docs do not claim a current PyPI version or Windows support | -| R-26 | `REQ-PROOFKIT-PACKAGE-001`, `REQ-PROOFKIT-PACKAGE-007` | extend package reference closure and mutable-release-fact scenarios / their separate falsifiers | `internal/tools/packageverify/main_test.go`, `TestPackagePublicReferenceClosure`, `TestVerifyNoStalePackageDocsRejectsMutableReleaseFactsInMarkdown`, `go test ./internal/tools/packageverify` | The bounded destination and exact code-span classifications are not a complete Markdown parser; the npm artifact is not a source checkout | -| R-27 | `REQ-PROOFKIT-SPEC-009`, `REQ-PROOFKIT-SPEC-021`, `REQ-PROOFKIT-QUALITY-004` | extend browser route and CLI ABI scenarios | `internal/command/requirementbrowser/requirementbrowser_test.go`, `TestInvalidViewListsEverySupportedView`, `go test ./internal/command/requirementbrowser ./internal/app` | Diagnostic parity does not prove browser runtime | -| R-28 | `REQ-PROOFKIT-QUALITY-025` | extend `proofkit.supply-chain-quality.workflow-source-oracles` / `proofkit.workflow.exact-source-oracle-falsifiers` | `scripts/workflow_source_oracles_test.go`, `TestExistingReleasePathIsReadOnlyAndFailsOnDrift`, `go test ./scripts -run ExistingRelease` | Source allowlist does not prove provider asset state | -| R-29 | `REQ-PROOFKIT-SPEC-021`, `REQ-PROOFKIT-PACKAGE-002` | extend fixed-launcher and one-shot-cleanup scenarios / their separate falsifiers | `internal/command/requirementbrowser/server_test.go`, `TestOpenBrowserUsesFixedLauncherAndLoopbackURL` plus the three `TestServeOneShot*` cleanup selectors, `go test ./internal/command/requirementbrowser` | Launcher success does not prove cleanup or browser rendering; cleanup does not prove browser profile identity | - -This change makes no selective-gate sufficiency claim. The repository has no -current aggregate producer that can derive a complete -`selective_gate_plan_input` from Git plus the binding graph, and inventing one -inside an audit-remediation PR would be a new feature. The exact closeout gate -is therefore always: - -```bash -git diff --check -npm run check -``` - -If an optional caller-owned selective input is materialized during -implementation, it is admitted with: - -```bash -go run ./cmd/agentic-proofkit selective-gate-plan --input /selective-gate-plan-input.json > /selective-gate-plan.json -jq -e '.unknownEdges == [] and .failures == []' /selective-gate-plan.json -``` - -Any non-empty `unknownEdges` or `failures` blocks closeout until the binding or -owner route is corrected. Running the full gate does not convert an unresolved -edge into success. Absence of an optional selective input remains an explicit -non-claim, not a skipped success. - -## Proof matrix - -| Repair | Current wrong implementation accepted by falsifier | Required narrow gate | -|---|---|---| -| R-01 | Observe-mode blocked prerequisite returns `passed/0` | `go test ./internal/command/adoptiondoctor ./internal/kernel/adoptionmode` | -| R-02 | Tool-only module appears as required root dependency | `go test ./internal/tools/releasesbom` | -| R-03 | Source, ancestor, or manifest-owning package swap can redirect the post-check open | `go test ./internal/command/publicapi` | -| R-04 | Output parent swap can redirect create or rename | `go test ./internal/app -run OutputWriter` | -| R-05 | Expected expression plus `|| true`, a shell override, a working-directory override, or any merge-critical `continue-on-error` field is admitted | `go test ./scripts -run 'WorkflowGuard|PackageGateWorkflowOracle'` | -| R-06 | Expected tests inside `if false` or under inherited/job/step execution overrides are admitted | `go test ./scripts -run RequiredAggregate` | -| R-07 | `{}` yields JSON stdout and empty stderr | `go test ./internal/app -run RequiredInputCommandsRoute` | -| R-08 | Entity-obfuscated visible overclaim passes | `go test ./internal/command/readinesscloseout` | -| R-09 | Input or output schema change leaves ABI hash unchanged | `npm run command-contract:check` and `go test ./internal/tools/commandcontractgen ./internal/app -run CLIContract` | -| R-10 | Breaking pre-1.0 patch is admitted | `go test ./internal/tools/releasechange ./internal/tools/releasepreflight` | -| R-11 | One self-hosting or package predicate can be inverted | Exact R-11a through R-11d gates in durable proof routing | -| R-12 | One closure field can be cleared without failure | `go test ./internal/tools/coveragemetrics` | -| R-13 | Floating external action ref is accepted | `go test ./scripts -run ExternalActions` | -| R-14 | Local install requires ambient PATH | `go test ./internal/tools/packageverify -run OnboardingTrace` | -| R-15 | Preset owner and help/contract diverge | `go test ./internal/command/stackpreset ./internal/app` | -| R-16 | Installed README example becomes invalid | `go test ./internal/tools/packageverify -run OnboardingTrace` | -| R-17 | Decision row has more than three cells | `go test ./internal/app -run ContractMap` | -| R-18..R-23 | Initial, failure, narrow, or alternate state escapes browser oracle | `npm run browser:check` with the named state-matrix rows | -| R-24 | Caller consistency is still called verified | `go test ./internal/command/requirementcontext ./internal/command/requirementdiff ./internal/command/requirementbrowser ./internal/app` | -| R-25 | Docs drift from Python/platform owners | `go test ./internal/tools/pythonpackage ./internal/kernel/releaseplatform` | -| R-26 | Installed artifact exposes a dangling source-checkout route | `go test ./internal/tools/packageverify -run PackagePublicReferenceClosure` and `npm run package:artifact` | -| R-27 | Renderer and app disagree on admitted views | `go test ./internal/command/requirementbrowser ./internal/app -run InvalidView` | -| R-28 | Existing release can enter `gh release upload` | `go test ./scripts -run ExistingRelease` | -| R-29 | Launcher can receive caller executable, arguments, or non-loopback URL | `go test ./internal/command/requirementbrowser -run OpenBrowser` | - -Final proof: - -```bash -git diff --check -npm run check -``` - -The final committed object must pass the full gate. Provider publication and -registry checks remain separately unverified unless external evidence is -available. - -## Implementation correction epoch - -Independent implementation review may falsify a design assumption without -expanding product scope. The following corrections are admitted because each -strengthens an existing invariant at its existing owner: - -| Correction | Falsified implication | Required correction | -|---|---|---| -| C-01 | `hash(path) = h` and `buildInfo(path) = b` do not imply that `h` and `b` describe the same bytes under a pathname swap or same-inode rewrite | Derive hash and build information from one immutable byte snapshot read through a pinned release-file descriptor, then prove same-handle content, pre/post descriptor, and current-path identity | -| C-02 | Cached bytes from read `A` plus identity from later stat `B`, or a parsed-export cache keyed only by canonical route, do not imply one immutable source admission across lexical aliases | Build each TypeScript lexical admission's bytes, digest, and identity from one pinned descriptor, bind every canonical source route to its first admitted identity/digest/parsed exports, and reject deterministic same-lexical or cross-alias drift | -| C-03 | A command-level native-source digest does not expose the required input/output compatibility surface | Initially require a recursively resolved field tree, then treat that attempted correction as falsified and superseded by C-09 after field-name inference produced false nested associations | -| C-04 | An AST function declaration with a matching name does not imply that `go test -run` can discover or execute it | Admit only Go test names and signatures using `*testing.T`, including valid import aliases, and require the witness to be an active `_test.go` file in the current `go list` projection | -| C-05 | A breaking version increment does not imply a `.0` target | Admit every major increase and every pre-1.0 minor increase while rejecting only breaking patch-class changes | -| C-06 | Updating the context producer alone does not own the wire contract of semantic diff and graph consumers | Include `SPEC-022` and `SPEC-023` as direct R-24 owners and proof routes | -| C-07 | A prohibited identifier appearing as an arbitrary substring of a content digest does not imply organization-policy leakage | Match prohibited identifiers at identifier boundaries and retain staged-blob plus worktree falsifiers | -| C-08 | A hard-coded receipt-kind mismatch does not remain a negative case after proof bindings legitimately gain that command | Derive the mismatch fixture from the current complement of the selected requirement's command set and require the complement to be non-empty | -| C-09 | Internally consistent inferred record graphs and executable test names do not imply semantic CLI-contract parity | Replace the false nested graph with an honest `root_shape_only` variant grammar, enumerate every supported JSON root/mode, use exact `union` roots instead of an unconstrained scalar-capable aggregate, cover omitted flag defaults as first-class conditions, add direct high-risk CLI root oracles, and deny nested/type/cardinality/nullability parity | -| C-10 | A repository-root-confined reopen does not preserve the package directory admitted with its manifest when an in-root sibling is substituted | Open the package boundary as a confined pinned sub-root before reading its manifest, then admit all package sources through that same handle | -| C-11 | A versioned breaking-release policy does not imply exact consumer pinning when generated release notes omit npm's exact-save flag | Render install and rollback commands with `--save-exact`, include the literal previous version, and falsify both generated routes | -| C-12 | A design table naming a selector or witness does not imply that the durable proof graph contains it | Bind every exact design selector and witness identity, then run binding admission and selector-executability closure | -| C-13 | A repository-root-confined output route does not preserve the admitted parent when that parent is replaced by an in-root sibling symlink | Open and pin the admitted parent as a confined sub-root, bind the route and handle with `SameFile`, keep temporary creation and cleanup on that handle, and recheck the current route; C-28 supersedes descriptor-relative publication | -| C-14 | Counting one externally blocked gap as also enforced does not imply disjoint adoption-doctor disposition classes | Derive blocked and enforceable gap sets independently, exclude blocked gaps from enforcement counts in every mode, and let unresolved blocked evidence determine top-level blocked state | -| C-15 | One native-source digest does not imply complete ownership when an output is assembled by two command packages | Admit exactly one of `nativeSource` or a sorted, non-empty, unique `nativeSources` list and require every declared source digest to be fresh | -| C-16 | A scanner parse failure represented as a synthetic semantic report does not imply structural-error channel parity | Return scanner admission errors structurally so malformed deployment input exits non-zero with empty `stdout` and a bounded `stderr` diagnostic | -| C-17 | Server cleanup and one-shot lifecycle tests do not imply that every terminal one-shot output has a declared public root shape | Declare distinct submitted and terminal variants and execute both through package and public-CLI oracles | -| C-18 | Variant-local root checks do not imply globally unambiguous conditions, default-flag coverage, or a reachable aggregate mode | Reject duplicate conditions across variants, exercise explicit and omitted defaults directly, and admit the two-input pilot aggregate through one strict envelope | -| C-19 | A worktree snapshot derived from tracked and untracked paths does not imply that an unstaged tracked deletion affects artifact identity | Subtract `git ls-files --deleted` from the snapshot and prove staging the same deletion cannot change the candidate snapshot | -| C-20 | Rejecting forbidden root entries does not imply that the canonical root package is retained by the test fixture | Include the valid root package entry in the positive artifact fixture, then mutate each forbidden root entry independently | -| C-21 | Recognizing literal `true` does not imply that a dynamic or constant GitHub expression cannot enable job-level `continue-on-error` | Require the fail-closed aggregate job to omit `continue-on-error` entirely and falsify literal, expression, and explicitly false field presence | -| C-22 | Bootstrap and terminal-state coverage does not imply coverage of per-view loading states that remain visible under a delayed request | Hold each specifications, diff, and graph request behind a deterministic barrier and run the complete accessibility, target-size, reflow, contrast, packet, and tab-order oracle before release | -| C-23 | Exact run text and actionlint success do not imply the same execution semantics when a typed oracle drops inherited or step-level environment, shell, and working-directory controls, partial truth evaluation does not prove `continue-on-error` absence, and ordinary nullable decoding does not preserve forbidden-key presence | Model workflow, job, and step run controls with presence-aware scalar admission; require exact safe workflow defaults and exact owner-reviewed environment entries where present plus exact bash defaults for the CI aggregate; forbid job defaults, unexpected environment entries, shell, working-directory, or any `continue-on-error` presence on all required leaf and aggregate jobs and on every step in each merge-critical job; falsify each inheritance level and explicit YAML `null` for forbidden scalar controls independently | -| C-24 | A release change record that correctly declares some breaking changes does not imply that it declares every intentional public state/exit change approved by the design | Declare the `adoption-doctor` blocked-prerequisite state and nonzero-exit change plus its consumer migration step in the versioned record, and bind a direct record-to-rendered-notes falsifier | -| C-25 | A declared top-level state/exit migration does not imply declaration of nested public rule-status changes, and an observe-only test does not prove every non-enforced rule path | Declare the non-enforced advisory rule transition from `passed` to `skipped` plus its consumer migration, require exact `observe=skipped` and `warn=warning` rule statuses, prove gaps outside an `enforce-touched` selection remain top-level `passed/0` with `skipped` rules, and prove the declaration reaches rendered notes | -| C-26 | `trace: retain-on-failure` and an error naming a run directory do not imply that diagnostics survive process cleanup or reach provider review | Under the QUALITY-022 browser artifact-confinement and failure-diagnostics-retention witnesses, clean successful browser runs, retain failed attempt directories, upload only the attempt-scoped report and test-results paths under an exact failure condition, and keep authoritative proof upload success-only | -| C-27 | `retain-on-failure` plus a pinned axe package does not imply bounded Firefox execution: neither removing continuous screenshots alone nor shrinking the repeated megabyte-scale builder source alone prevents the stall | Initialize the pinned version-identical minified axe distribution through the browser-context script channel, let every builder evaluate only a constant no-op loader, retain action, DOM, network, and source trace data with continuous screenshots disabled, request one bounded best-effort screenshot after failure, preserve all default and target-size rule semantics, and require clean first-attempt execution in every pinned engine without retries or a larger timeout | -| C-28 | A pinned parent handle plus current-route checks does not imply that descriptor-relative publication stays repository-confined when the admitted parent moves after the last check | Keep temporary-file creation and cleanup through the pinned parent, publish through the repository root with full temporary-source and destination routes, and use an exact `before_publish` barrier to prove outside-root and in-root replacement sentinels remain unchanged with no temporary residue | -| C-29 | Two root-confined pathname operands do not imply that `os.Root.Rename` publishes the admitted temporary object into the admitted parent: the source and destination routes are resolved separately, and no additional pre/post pathname check closes the remaining same-user namespace race | Stage the temporary object directly under the repository root, retain its identity, re-admit both temporary identity and destination-parent route after the exact barrier, publish through the repository root, and replace the unprovable stable-parent concurrency promise with an explicit same-user post-admission namespace-mutation non-claim; this narrows only an unmerged audit hardening claim absent from the baseline contract | -| C-30 | Root-level staging does not preserve baseline writable-child or nested-filesystem behavior; `SameFile` does not imply immutable temporary content or mode, that a followed symlink is the directory entry being renamed, or that a parent admitted before hashing remains current at publication; and `FileMode.Perm() == 0644` does not exclude setuid, setgid, or sticky bits | Under the explicit same-user concurrency non-claim, stage, validate, publish, and clean through the pinned destination parent; after the exact barrier re-admit the non-symlink temporary entry identity, complete mode exactly equal to `0644`, and content digest, then re-admit the parent route at the irreversible rename boundary, and falsify object replacement, permission and every special-mode-bit change, in-place rewrite, symlink aliasing, and parent replacement before publication | -| C-31 | A compatible public requirement addition does not reach consumers merely because its source, overview, binding, and tests agree | Add the repository-confined same-parent atomic output guarantee to the versioned machine release record and require its exact summary in rendered notes | -| C-32 | Byte equality and a valid provider projection do not imply that the final PR body includes the promised final tree, diff counts, local gates, residual non-claims, and retrospective | Build one canonical closeout record from exact repository and admitted local-evidence facts, embed it with a digest and unique sentinels, and validate it independently in both the reviewed snapshot and final server body | -| C-33 | Validating a mutable local artifact path and later rereading that path for projection does not imply that the projected bytes were admitted; permission-only file-mode equality also does not imply exact mode equality | Copy each local evidence object once into a private snapshot, validate and project only those same bytes, recheck final `HEAD` and tracked-tree cleanliness after record construction, compare the complete temporary-file mode to exactly `0644`, and falsify each special mode bit independently | -| C-34 | Snapshot byte identity does not admit a field that its validation predicate ignores | Project only exact validated local fields into closeout; omit the browser input digest and package/coverage snapshot digests because this closeout has no owner-valid predicate for their semantics | -| C-35 | For a multi-document JSON stream, `jq -e predicate` returns the truth status of the last output and therefore does not imply that the first document later selected by `--slurpfile` passed | Slurp each local snapshot during validation, require exactly one document, apply the predicate to that document, and admit the exact sorted Chromium/Firefox/WebKit project inventory | -| C-36 | Rejecting only literal `false` or `0` does not imply that a trust-significant workflow job or step is reachable; a dynamically false CI step can leave its containing job successful and satisfy the aggregate, while nullable decoding can confuse explicit `if: null` with absence | Require an exact closed job inventory, presence-aware condition decoding, and exact absent-or-owner condition for every job and step in required CI and release workflows; retain exact named conditional exceptions only; and falsify dynamic false plus explicit-null conditions on required CI and release routes | -| C-37 | A successful job whose identifier is `platform-smoke` does not imply macOS platform execution, and rejection-only binding selectors can pass vacuously when their owner workflow is already invalid | Bind each required CI job to its exact provider-check name and runner, require the exact fail-closed platform-smoke command, reject reusable-job and no-op substitution, assert positive owner admission before every bound mutation table, and route the positive inventory oracle directly through QUALITY-011 and QUALITY-013 | -| C-38 | An exact final platform command does not imply that an earlier step did not rewrite its package-script owner; lossy runner-label normalization does not prove an exact scalar; negative package-gate selectors do not imply that the real CI and release owners pass; and an unparsed closeout snippet can regress its singleton predicate | Close the ordered five-step macOS inventory and exact package-script owner command, compare each runner as an exact string scalar, bind a positive oracle that admits both real package-gate workflows, restore `length == 1`, and execute the affected selectors and closeout filter | -| C-39 | An exact local-action path does not imply exact repository-controlled action semantics, and zero-value decoding does not distinguish an absent step key from `null`, empty, or whitespace-bearing dual execution syntax | Admit the exact setup-action bytes by digest, reject a nested semantic-shadow mutation, track `run`, `uses`, and `with` key presence, compare the exact YAML block value including its terminal newline, and reject dual or explicit-empty execution keys | -| C-40 | Valid selector functions and commands do not imply that the complete anti-vacuity proof set remains bound when a critical selector is removed; whitespace field splitting does not imply Bash-to-direct-exec argv equivalence for quoted literal words | Extend the existing exact selector inventory to the QUALITY-011 and QUALITY-013 scenarios and independently falsify missing and surplus selectors; replace whitespace splitting only at the README boundary with a bounded expansion-free literal shell-word lexer that preserves safe quotes, escapes, and concatenation while rejecting operators, expansion, globbing, line continuation, and malformed quotes | -| C-41 | An exact selector inventory keyed only by scenario identity does not imply that the scenario remains bound to its owning requirement | Key every protected selector inventory by the exact `(requirementId, scenarioId)` pair and falsify owner-only transfer independently for each newly protected anti-vacuity scenario | -| C-42 | Marking a required scenario seen before an empty-selector early return does not imply exact-set admission; a lexer accepting NUL cannot preserve direct-exec argv; and a P0-P2-only review threshold does not imply the plan's stronger no-unresolved-finding completion criterion | Compare every required selector set before the generic empty path and falsify complete deletion for each critical scenario; reject NUL in every lexer state and preserve the mutant; align all final review thresholds to no unresolved confirmed finding | -| C-43 | Unicode whitespace trimming does not preserve Bash IFS or JSON whitespace semantics; a top-level NUL check does not inspect a byte consumed as an escape lookahead; and double quotes do not suppress interactive Bash history expansion | Trim only Bash space/tab delimiters around the command, preserve all other literal command bytes and exact JSON fence bytes, reject escaped NUL, reject unescaped `!` inside double quotes, and preserve Bash-equivalent `\\!` as a literal backslash-plus-exclamation argument | -| C-44 | Symmetric trimming of Bash delimiters does not distinguish an unescaped trailing separator from a trailing separator escaped into the final argv word | Trim only leading space/tab before the fixed command prefix, let the lexer discard unescaped trailing delimiters, and preserve escaped trailing space and tab with direct Bash-equivalent argv mutants | -| C-45 | A missing-function mutant on a newly exact-set-protected scenario does not imply that generic function-existence validation remains independently exercised because exact-set admission now rejects it first | Route the existing missing-function mutant through an unprotected selector-bearing binding while retaining the separate exact-set missing, empty, surplus, and owner-transfer mutants | -| C-46 | Consuming one escaped `!` does not preserve Bash semantics for two or more preceding backslashes because history suppression precedes quoted or unquoted backslash collapse | Consume the complete backslash run before `!`, project `ceil(n/2)` backslashes when double quoted and `floor(n/2)` when unquoted, retain a literal `!`, and preserve mutants for both states at run lengths one through four | -| C-47 | Aligning final committed-object review thresholds does not prevent an earlier candidate-preparation P0-P2 cutoff from dropping a confirmed P3 before those reviews | Require exact evidence for every remaining confirmed objection and finding at candidate preparation | -| C-48 | A correct pure inventory mutant does not justify repeating generic AST parsing and `go list` discovery before reaching that mutant, and repeated I/O obscures the boundary being tested | Run exact requirement/scenario/selector inventory admission as a complete pure first phase, then run the existing generic selector-function and active-file validation once for the positive object | -| C-49 | Scanning the whole remaining backslash run only when looking for a history marker, then advancing by one pair when no marker exists, yields `n + (n-2) + ... = O(n^2)` work on a package-bounded command line | Consume each complete backslash run exactly once, dispatch its terminal byte with the C-46 quoted/unquoted semantics, and preserve a 128-KiB non-history run regression | -| C-50 | Moving global inventory admission before generic executability does not imply that partial generic fixtures contain the repository-global inventory, and forcing them to do so would couple local error tests to unrelated owners | Keep one production composition function, expose one pure exact-inventory phase and one generic executability phase locally, and route partial fixtures only to the phase whose error they falsify | -| C-51 | Source presence and full-package execution of generic missing-function and invalid-signature tests do not imply that selective QUALITY-010 proof routing invokes those two executability conjuncts | Bind both selectors to the QUALITY-010 executability scenario, close its exact four-selector inventory, and run empty, missing, surplus, and owner-transfer mutants against that scenario too | -| C-52 | Binding a representative subset of typed workflow-oracle tests does not imply selective proof for permission floors, prior-step order and command identity, duplicate names, need-success bypasses, admitted optional paths, or typed needs normalization | Classify every owner-relevant test in `workflow_package_gate_oracle_test.go`, bind the complete seventeen-selector QUALITY-013 surface, and make that exact set part of the protected requirement/scenario inventory | -| C-53 | A source-level intentional semantic change does not imply inclusion in the closed release record, and requiring one named Go-test parameter rejects the toolchain-valid unnamed form | Declare the readiness-closeout character-reference behavior as a breaking change with migration guidance and an exact rendered-note witness; admit zero or one Go-test parameter name, bind the unnamed-parameter regression, expand QUALITY-010 executability to five exact selectors, and protect the three-selector QUALITY-024 release-record inventory | -| C-54 | Decoding before structural Markdown parsing can create a false cell boundary; a body-level UI state does not imply coverage of a distinct stable content substate; and generic release prose does not declare specific runtime additions or a removed keyboard contract | Split original Markdown bytes before one-pass text decoding and falsify an encoded pipe; add specifications no-match to the full browser matrix with exact content-state identity; declare Arrow-key removal with migration guidance plus pilot-all and witness-selector additions, project every exact record entry into rendered notes, and refresh the readiness native-source, generated CLI-contract, and public ABI golden projections | -| C-55 | Showing a bare family-discovery executable in root help does not imply that an exact-tarball npm consumer can execute it, and searching stdout before running a separately hard-coded argv is a false-green continuity witness | Project the copyable offline npm exec route, parse and execute the displayed route through the installed consumer, reject the bare-route mutant before family discovery, and refresh the app native-source, generated CLI-contract, and public ABI golden projections | -| C-56 | Unicode whitespace normalization before parsing a displayed shell command does not preserve copied Bash argv because NBSP is not an IFS delimiter | Remove only authored leading ASCII space/tab indentation, preserve all trailing bytes, and reject leading and trailing NBSP mutants | -| C-57 | Executing one displayed root-help command does not imply a continuous installed-user route when family, leaf, preset, and README transitions are rediscovered or hard-coded by the witness | Display a copyable offline npm command at every help transition, parse and execute those exact bytes, execute every contract-owned preset route, discover the installed README path from help, and reject bare or missing intermediate routes | -| C-58 | Source implementation and generic release prose do not imply a closed, correctly classified consumer change record | Protect the exact onboarding addition, classify removal of installed `AGENTS.md` and `CONTRIBUTING.md` as breaking with migration guidance, and extend absolute-symlink migration to manifest ancestors plus source paths | -| C-59 | Exact required step identities do not imply that an earlier unreviewed step cannot rewrite their npm-script owners | Hash the complete ordered semantic step inventory for CI `source-quality`, CI `browser-runtime`, and release `candidate`, including execution-key presence and values, and reject inserted script-shadow steps in all three jobs | -| C-60 | File size alone does not prove a god file, but five independent owners plus a demonstrated missed invariant do; conversely, one physical witness path shared by exact QUALITY-011 and QUALITY-013 selector inventories prevents a sound one-owner-per-file split | Inventory every tracked file at or above 1,000 LOC or 65,536 bytes; separate browser, runtime-precondition, source-policy, scanner-policy, and neutral support files; retain the QUALITY-011/013 cluster; update exact binding paths and protect moved selectors against deletion, owner transfer, surplus, and stale-path substitution | -| C-61 | A hash over selected step fields does not imply exact semantic step identity when valid GitHub step fields are omitted | Include `id` and `timeout-minutes` values and key presence in every closed step projection, and reject each independently in CI source quality, CI browser runtime, and the release candidate | -| C-62 | Exact requirement, scenario, and selector equality does not imply that selectors remain in the file reviewed as their owner | Add one exact `witnessPath` inventory for every protected selector set and reject relocation independently from missing, surplus, and owner-transfer mutations | -| C-63 | A reviewed dirty diff does not imply that candidate staging contains new untracked owner files | Close the untracked candidate inventory to the five extracted workflow files, stage the complete reviewed path union, and require staged-path equality plus an empty unstaged and untracked remainder | -| C-64 | A threshold-complete path set does not imply that recorded file measurements are exact after surrounding edits | Recompute LOC and bytes from the final candidate snapshot, correct the stale `internal/app/app_test.go` byte count, and require exact final ledger equality before review | -| C-65 | Extracting scanner tests into a cohesive file does not imply selective proof ownership when no requirement binding names the new selectors | Bind CodeQL, OSV, and Scorecard permission/publication invariants to the extracted scanner owner, admit their exact selectors and path, and falsify deletion, surplus, owner transfer, and relocation | -| C-66 | Adding an installed invocation to generated help does not imply that every shipped leaf exposes valid copyable bytes in the exact tarball | Traverse every displayed family and leaf route from the installed artifact, execute each exact help route, compare each installed invocation to its exact bare usage, and retain preset and README transitions as additional end-to-end witnesses | -| C-67 | A logically correct staging predicate is not an executable plan when its temporary-file cleanup is rejected by the active destructive-action guard | Hold the reviewed, expected-untracked, actual-untracked, and staged path inventories in memory; pass the exact untracked inventory to `git add` through stdin; preserve equality and empty-remainder checks without temporary paths or cleanup | -| C-68 | Absence of an observed literal write scope does not imply an explicitly declared read-only scanner floor, and one required provider write scope does not exclude surplus write authority | Require exact workflow, advisory-job, and provider-job permission maps for CodeQL, OSV, advisory Scorecard, and public Scorecard; preserve intentional inheritance explicitly; reject a missing workflow floor, advisory write authority, and surplus provider write authority independently | -| C-69 | Comparing Installed invocation while scanning lines does not imply comparison with the final unique Usage line, and raw string prefix does not preserve the command-token boundary | Collect the unique Usage and Installed invocation before comparison, require their exact authored order, admit only exact command token or token-plus-space, compare full installed bytes afterward, and reject installed-before-Usage plus prefix-collision mutants | -| C-70 | A full package test containing C-69 does not imply that selective QUALITY-019 proof preserves or executes that falsifier | Add C-69 to the exact installed-package selector set and witness path, strengthen the owner requirement to name every leaf and exact ordered command-token binding, and reject selector deletion, surplus, owner transfer, and path relocation | -| C-71 | A passing current browser matrix does not imply that the final provider-closeout predicate admits that matrix when its per-project count is stale | Bind the executable closeout snapshot predicate to the current owner count of 31 passed tests for each of Chromium, Firefox, and WebKit before publishing or reporting provider closure | -| C-72 | Unique condition strings do not imply mutually exclusive machine-selectable CLI variants, and raw argv does not imply one selector state while repeated single-value flags use last-write-wins | Admit an optional canonical complete CLI-flag conjunction model; require equal allowed dimensions and disjoint assignments; close all twelve valid adoption output states against native option admission; derive every ABI condition from parsed argv; reject repeated `--mode` and `--pilot`; and project the intentional behavior change through the release record | -| C-73 | An exact decomposition-owner inventory remains exact only for the snapshot from which it was derived; a later owner-aligned split can add a required file without changing the earlier five-file predicate | Extend the decomposition-owner subset to the six files including `internal/tools/commandcontractgen/condition_model.go`, and retain exact staged-path equality plus empty unstaged and untracked remainders | -| C-74 | A valued flag being present in raw argv does not imply a literal condition state when its parser admits the empty string and native normalization uses empty as the omission sentinel | Reject an explicitly empty `--pilot` value before option construction, bind the exact CLI diagnostic and condition non-projection, and disclose the intentional rejection with migration guidance | -| C-75 | The decomposition-owner subset does not imply the set currently untracked after an earlier candidate commit has already admitted five members of that subset | In the correction amend, require the current untracked set to contain only `condition_model.go`, stage the exact reviewed correction-path union, and independently require all six decomposition owners to be present in the baseline-relative additions | -| C-76 | Enumerating hard-coded native mode and pilot literals in a test does not imply future closure against the native admission domain when that owner changes and its source digest is refreshed | Build native admission maps and immutable test projections from one internal mode list and one pilot list; derive the closure loops from those projections and retain exact 80-combination and twelve-valid-state assertions | -| C-77 | A generic canonical/disjoint condition grammar does not imply generic finite native-option or argv closure when only one current definition owns those executable witnesses | Admit `cli_flag_conjunction_v1` only on the adoption output definition, reject an unowned second opt-in, and require a future owner to extend generator admission together with its own native-domain and argv proof | -| C-78 | A complete owner-specific subset of added files does not imply the complete candidate added-file inventory relative to the reviewed baseline | Require exact equality with the complete baseline-relative added-path inventory after final correction freeze and separately prove that the six decomposition owners are a subset | -| C-79 | Covering all twelve valid normalized assignments does not imply that every exercised argv which emits JSON is bound to its exact condition and root variant | Bind the reachable guidance mode/scope failure argv to the guidance-report condition and `06-guidance-report` root before asserting its failure body | -| C-80 | Restricting a condition model by definition ID does not imply that another command or direction cannot reference that admitted definition | Require the exact `(adoption-contract-envelope, output, adoption-output-definition)` triple and reject command, direction, and definition aliases independently | -| C-81 | Exact source sizes recorded in explanatory prose do not remain exact after later corrections, even when the threshold ledger is refreshed | Remove volatile inline line counts from the decomposition rationale and keep snapshot-specific measurements only in the deterministically validated ledger | -| C-82 | Adding condition and variant coordinates to one JSON test case does not imply their retention when conditional assertions let both coordinates be deleted and a duplicate condition preserves the global count | Require `assertJSON` if and only if both route coordinates are non-empty for each case, then run argv-condition and variant-root assertions unconditionally on every JSON case | -| C-83 | A biconditional among mutable fixture fields does not imply that the fixture matches actual runtime emission, and deleting the whole case evades per-case checks | Require observed non-empty stdout exactly when a JSON assertion is declared, parse every observed stdout, and preserve the exact sorted fourteen-case JSON inventory | -| C-84 | A cohesive file boundary does not justify duplicating an identical generic algorithm already owned by the same Go package | Replace all condition-model calls with the existing `sortedKeys` helper and delete the duplicate helper plus unused import | -| C-85 | A critical binding whose selector exists and executes does not imply that the selected test reaches the claimed byte-admission invariant | Replace the README-only selector on the Mach-O compatibility scenario with the exact four-test negative/positive/parser inventory, bind its exact witness path in the coverage owner, and apply the existing empty, missing, surplus, owner-transfer, and relocation mutations | -| C-86 | Closing one semantic false route does not imply that other exact selectors reach the operation named by their scenario | For Python wheel-platform parity, bind README ownership, exact all-target wheel metadata/filename projection, and verifier rejection; for browser one-shot cleanup, replace the fixed-launcher selector with the exact three cleanup/concurrency tests while retaining the launcher in its separate package-boundary scenario; protect both exact selector/path inventories with the existing five mutation classes | -| C-87 | A package-reference closure test can pass while every mutable-release-fact detector remains unreachable | Replace the PACKAGE-007 selector with the existing ten-class stale-fact falsifier, retain reference closure only in its PACKAGE-001 scenario, and protect the exact requirement/scenario/selector/path tuple with empty, missing, surplus, owner-transfer, and relocation mutations | -| C-88 | Exact permissions for every expected scanner job do not imply that no unclassified write-capable job exists | Require the workflow job count to equal the disjoint advisory/provider expectation count before validating each expected job, and reject an added unclassified `contents: write` job for CodeQL, OSV, and Scorecard | -| C-89 | A canonical installed help route does not imply that generated continuations resolve the artifact in every supported launcher channel | Admit one immutable `npm_offline`, `python_module`, or `path` invocation profile at the launcher boundary; thread one renderer through the exact generated-field inventory; preserve caller-owned strings; execute exact npm and wheel continuations; reject wrong-profile, bare, missing, surplus, and caller-rewrite mutants; bind `PACKAGE-002`, `PACKAGE-003`, `PACKAGE-006`, `QUALITY-019`, and the public-string migration under `QUALITY-024` | -| C-90 | An executable native-output selector does not imply that the selected test observes the declared output root or its complete native owner | Close the root-distinct output inventory over adoption-contract-envelope, pilot-admission, and self-check; bind them respectively to `internal/app/cli_abi_test.go` selectors `TestAdoptionContractEnvelopeCLIABI`, `TestStandaloneMultiVariantCommandsUseExactRootShapes`, and `TestSelfCheckOutputUsesExactRootShape`; require exact native source sets adoption=`{internal/command/adoptioncontract}`, pilot=`{internal/app, internal/command/pilotadmission}`, self-check=`{internal/app}`; protect source sets and exact command/direction/path/test/executable-command/requirement/scenario tuples with empty, missing, surplus, substitution, relocation, direction-transfer, scenario-transfer, command-drift, and owner-transfer mutants under `PACKAGE-002`, `QUALITY-004`, and `SPEC-011` | -| C-91 | An absolute launcher path does not imply that the value is safe to project into generated commands or diagnostics | Reject report-visible secret-like and Unicode control content at Python launcher admission, use field-only errors that never disclose the value, and exercise the shared redaction corpus plus control mutants | -| C-92 | Closing continuation string fields does not imply closure of help display routes, structured argv, project workflow identity, or the installed wheel's complete next-step chain | Bind every owned help/display/argv sink to one immutable renderer, preserve caller-owned native-witness argv, build project workflow plans and source-report hashes with that renderer, bind `TestGeneratedCommandInvocationProfileRouteClosure`, traverse every installed wheel family/leaf route, execute exact emitted agent-route argv directly, and reject indentation, Unicode-whitespace, expansion, missing, surplus, relocation, and wrong-profile mutants | -| C-93 | Removing a temporary file and immediately recreating its pathname does not imply a different `os.SameFile` identity because Linux may reuse the released inode | Pre-create a replacement with the exact expected bytes and mode while the writer's temporary object is still live, assert that the two coexisting objects have distinct identities, then remove the temporary entry and rename that replacement into its pathname at the deterministic barrier; identity is the only changed dimension and the exact diagnostic becomes platform-independent | -| C-94 | A fully rendered DOM and prior successful locator reads do not imply that a Firefox page-realm `evaluateAll` returns within the bounded test budget | Express ordered graph/table equality as retryable row-count equality plus an indexed `data-identity` equality for every expected element; reject missing, surplus, reordered, duplicated, substituted, and absent identities while retaining one worker, zero retries, and the 30-second timeout | -| C-95 | An exact size ledger for one frozen snapshot does not remain exact after a later static-analysis or provider correction | Recompute the complete `LOC >= 1000 or bytes >= 65536` ledger only after final source and document bytes freeze, then require exact path, LOC, and byte equality before committed-object review | -| C-96 | Typed YAML decoding does not imply that execution-affecting or unknown workflow keys were absent, because unmodeled keys can be discarded before semantic validation | Admit every tracked workflow through a raw `yaml.Node` closed-key oracle at workflow, job, and step scope; permit only the two exact owner-reviewed release environments; reject unknown, duplicate, merge, container, strategy, service, output, job-concurrency, reusable-job, and step-execution-control mutants before typed decoding | -| C-97 | A source-hygiene scanner that covers several text extensions does not imply closure over the authored text languages actually shipped as embedded browser assets | Add CSS to the admitted text extension set and derive staged-blob and worktree mutants from every unique extension in the exact tracked browser-asset inventory, while leaving identifier boundaries and digest-substring admission unchanged | -| C-98 | Removing one bulk Firefox page-realm evaluation does not imply that repeated manual `waitForFunction` plus `evaluate` range synthesis in adjacent selection scenarios is bounded; two immutable provider attempts stalled on different selection tests | Create and collapse ordinary native selection through Playwright `selectText` and click actions; for the nonzero Unicode range, compute strict UTF-16 and code-point bounds independently in the test runner and perform one locator-scoped exact-range operation without query scanning, clamping, or manual event dispatch; delete the shared helper without changing production code, test identities, retries, worker count, or timeout | -| C-99 | The historical audit baseline does not imply the current Git parent, remote `main`, pull-request base, provider base, or feature lease after independently reviewed dependency merges and correction publications | Preserve `3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64` only as `audit_baseline_sha`; require the singleton parent and remote `main` to equal integration base `0df4c28bac9737f476f7dc66030363b8b40d5417`; bind each publication to its exact observed feature lease, beginning with `1a681c47911680d101d36b48ce818ea1905a7148` for the first publication; after every push and before PR-body or provider mutation require exact PR author, head, base, open state, and non-merge state; use the integration base for PR diff, closeout, and provider projections | -| C-100 | A previously valid coverage count does not imply the final regenerated artifact retains that count after new bound scenarios | Require the final coverage snapshot to contain exactly 69 requirements, 69 bound requirements, 173 scenarios, and 78 commands; reject the stale value 167 and adjacent 172/174 counterexamples | -| C-101 | `unicode.IsControl` rejection does not imply rejection of report-visible Unicode format controls because bidi controls are category `Cf`, not `Cc` | Reject both Unicode `Cc` and `Cf` at the existing Python launcher boundary, retain field-only diagnostics, and exhaustively equate the admitted category predicate and rejection/non-disclosure oracle with every Unicode scalar in `Cc or Cf`, including non-bidi `Cf` values | -| C-102 | Listing a public alias in `allowedFlags` does not imply that every accepted alias composition has an exact input/output condition, and sequential parsing does not make repeated selectors unambiguous | Retain the compatible `--stack-diverse` alias, declare `--contract-envelope --stack-diverse` in the exact pilot input/output variants, execute that route through the public CLI oracle, reject repeated or mixed `--pilot`/`--stack-diverse` selectors in either order before reading input, and project the newly rejected formerly accepted argv through QUALITY-004 plus the breaking change record and migration | -| C-103 | A bound test that checks selected release-record entries does not imply that the complete reviewed machine declaration is closed; structural admission and regenerated notes can remain consistent after semantic deletion or surplus | Require ordered equality for every current breaking change ID/summary, addition ID/summary, and migration step plus byte-for-byte equality with one independently authored complete current release-note projection; reject per-entry deletion plus ID/summary substitution, every adjacent reorder, one valid machine surplus per inventory, reordered or relocated bullets, in-block surplus, and appended surplus, duplicate, or second owned sections through the existing QUALITY-024 selector | -| C-104 | Quoting a shell variable does not imply that an immediately adjacent colon is literal in zsh; `"$final_sha:refs/..."` expands a modified parameter and makes the reviewed publication step non-executable | Use `"${final_sha}:refs/..."`, scan the complete tracked plan for unbraced variable-colon forms, and bind publication to the corrected literal refspec plus the existing exact remote lease; add no repository-wide shell abstraction or permanent product gate | -| C-105 | A feature lease admitted before one successful force-with-lease publication does not remain current after that publication or a later amend | Preserve `1a681c47911680d101d36b48ce818ea1905a7148` as the historical first-publication lease, require the next correction publication lease to equal current remote head `90090a5c712efa70b900fed0e115274cfa4773f0`, and reject any mismatch before mutation; retain one literal final SHA as the new publication result | -| C-106 | An exact baseline-relative added-file inventory does not remain complete when later correction epochs add owner tests after the inventory snapshot, and a historical untracked-file predicate does not describe the current amend | Recompute the final inventory as 21 exact paths, add the CLI output-witness, invocation-profile, and Python continuation tests, require the C-106 correction path set to equal only the two reviewed design/plan files with no untracked remainder, and retain the six-file decomposition subset proof | -| C-107 | Checking each required Scorecard output input value does not imply that the complete `with` input set is exact; an added authority-bearing input can preserve every positive assertion | Require the exact three-key input set and values for `publish_results`, `results_file`, and `results_format`, and make the same selector reject a surplus `repo_token` mutant | -| C-108 | A manually listed requirement-delta claim does not imply parity with the actual base-to-candidate invariant map | Recompute the exact changed invariant IDs, restore `SPEC-011` and `QUALITY-005`, `QUALITY-006`, and `QUALITY-007`, and require the P10 declaration to contain all 30 changed IDs with no surplus | -| C-109 | Declaring that volatile inline measurements were removed does not imply that the decomposition rationale contains no stale numeric qualifiers | Remove the numeric qualifiers from all four small-file rationale bullets and retain snapshot-specific sizes only in the threshold ledger | -| C-110 | A generic truth-expression normalizer does not prove exact YAML boolean identity; the string `true }}` can normalize to truth while changing the owned action input | Require `publish_results` to decode as the literal boolean `true`, remove the unused generic helper, and make the same selector reject the string-substitution mutant | -| C-111 | A correction set described as current or latest without an epoch qualifier does not remain current after a later correction changes another owner file | Time-index every historical two-file and 21-path statement to the C-106 freeze and reserve that epoch's correction authority for P12.2's then-current exact three-file C-107 through C-114 set | -| C-112 | Exact inputs on one named Scorecard step do not imply that no second Scorecard action carries parallel publication authority | Require the selected named step to be the sole `ossf/scorecard-action` step and make the same boundary predicate reject a differently named second-action mutant with surplus `repo_token` input | -| C-113 | A lowercase literal prefix does not classify the complete Scorecard-action subset because GitHub owner/repository identity is case-insensitive | Split one action reference at its single `@`, compare only the owner/repository portion with ASCII case-insensitive equality, preserve the ref bytes, and reject a mixed-case second-action mutant while excluding distinct repositories, subpaths, and malformed references | -| C-114 | Unicode simple case folding is strictly broader than ASCII provider repository identity | Reject any non-ASCII repository byte before case-insensitive comparison and make a long-s repository mutant fail while retaining valid ASCII case variants | -| C-115 | Context initialization plus a constant wrapper loader does not imply bounded execution when source inspection proves that the wrapper still performs avoidable page-realm evaluations, creates a temporary page, and re-executes the context init script for each audit; deleting only the auxiliary version probe would preserve that higher-exposure topology | Remove `@axe-core/playwright`; initialize the exact pinned `axe-core` source once per test context; fail closed unless `page.frames()` is exactly the singleton `[page.mainFrame()]`; in one direct evaluation preserve the wrapper's same-origin and `playwright` branding configuration, require the pinned pre-run version, and run default rules plus explicitly enabled `target-size` through an exact closed options object; require returned `testEngine` name `axe-core` and the pinned version; preserve rule applicability and add one combined negative fixture that requires both a `target-size` violation on an undersized named control and a default `button-name` violation on a normal-sized unnamed control; isolate zero-frame, child-frame, absent/wrong pre-run version with no run, exact configure and run-options closure, absent/wrong result name or version, one-evaluation, and no-temporary-page mutants; preserve one worker, zero retries, the 30-second timeout, and the existing diagnostic policy | -| C-116 | Fourteen preceding passes do not imply bounded first-attempt graph proof: on immutable iteration 15 the visible SVG locator resolved but `boundingBox()` never returned before the 30-second deadline; moreover, DOM-derived expected identities, viewBox, visibility, minimum width, non-`none` first-edge stroke, and a PNG byte threshold do not imply the admitted local SVG contract because fixture-equal cache, omission, opacity-zero, hidden-descendant, hidden-overflow, hidden trust-state tables, one-pixel-height, geometry-override, individual-transform, phase-split animation, delayed transition, external SMIL, zoom, content-visibility, text-security, empty-label, text-layout, zero-dash, and degenerate-edge mutants survive weaker or circular oracles | Remove pass-path `boundingBox`, raw computed-style evaluation, and element screenshot calls; intercept the exact one app-issued same-origin graph POST, inject a safe response sentinel absent upstream, observe that response, and require its frozen ordered node and edge records plus sentinel to equal graph metadata, exact leaf topology, SVG children, and visible opaque graph tables; independently compute the viewBox, node geometry, labels, and non-degenerate edge coordinates from response primitives and the restated layout formula; require visible owned viewport and SVG, exact viewport scrolling, explicit local display, visibility, opacity, complete transform-family, no CSS animation or transition time on the owned ancestor chain, no declarative SVG animation in the document, zoom, content visibility, filter, clip-path, and mask values, `800px` minimum width, computed height of at least `180px`, exact visible and opaque node shape/label attributes, computed geometry, direct text, text security, and bounded text-layout serializations, and positive visible opaque non-dashed `1.5px` stroke on every childless edge without applying a bounding-box visibility matcher to zero-thickness SVG lines; retain failure-only diagnostics, one worker, zero retries, and 30 seconds; restart the complete immutable 30-process Firefox epoch from iteration 1 | -| C-117 | A successful `chmod` call does not imply that a setgid mutant materialized when Darwin clears the bit for a file whose group is outside the caller's groups | Normalize only the setgid test root to the effective group before temp creation, require exact post-`chmod` mode materialization for every mode mutant, and leave the correctly fail-closed writer unchanged | -| C-118 | A handoff submitted from one rendered view does not imply that its later response still owns the workspace view-state projection after the user opens a newer view | Capture the active view request identity at submission; always render the admitted handoff result in its independent packet region, but update the global view-state projection only when that view identity is still current; preserve server submission and do not pretend client abort can revoke an already committed handoff | -| C-119 | A fully rendered document with complete successful local resources does not imply that Playwright Firefox will report either `load` or `domcontentloaded` before the unchanged test deadline | Route every workspace open and reload through one web-first readiness policy that waits only for navigation commit, requires a successful main-resource response and the exact visible server-owned workspace heading, and leaves the existing state, accessibility, and API assertions as the behavior oracles; retain the raw `about:blank` negative-control navigation and add no retry or timeout increase | -| C-120 | A guard present in a browser helper does not imply that the executable proof depends on it: deleting C-119's HTTP-response guard or weakening its exact heading matcher leaves an all-success navigation matrix green | Add independent main-document 503 falsifiers for both open and reload plus a substring-preserving accessible-name drift falsifier; require every pinned engine to reject each mutant while leaving production code, retry policy, and timeouts unchanged | -| C-121 | A main document received with status 200 and a fully initialized visible workspace does not imply that Playwright resolves even its `commit` lifecycle wait | Arm one exact-URL navigation-response waiter before scheduling location assignment or reload; admit an exact trigger token, successful response, and exact visible heading; exclude same-URL non-navigation responses with an executable 503 decoy; statically exclude provider-falsified direct lifecycle waits from the workspace corpus | -| C-122 | One current correction inventory stated in prose does not equal a later executable inventory merely because both appear in P12.2, and an inventory does not remain current across an amend | Time-index the four-path C-121 staging set to its `c2315fd` epoch and make both current P12.2 owner surfaces name the same exact three post-`c2315fd` correction paths before staging | -| C-123 | Rejecting raw base-URL drift in source does not imply that browser proof depends on every local-origin clause | In the existing navigation test, admit the configured URL and reject non-string input, HTTPS, hostname drift, missing port, username, password, path, query, and fragment independently before any navigation | -| C-124 | Exact trigger-token admission and pending-waiter cleanup present in source do not imply executable dependence when every trigger succeeds and every failure follows a settled response | Inject one deterministic pending response into the existing navigation test; return a wrong token; require the exact token error, observed abort signal, and observed consumption of the waiter rejection; make the fallback response fail distinctly if token admission is deleted | - -Exact C-90 binding mappings use -`proofkit.package-boundary.cli-output-root-witnesses` for all three selectors, -`proofkit.supply-chain-quality.cli-abi-golden` for all three selectors, and -`proofkit.spec-proof-core.adoption-contract-envelope-cli-abi` for the adoption -selector. The independent tuple-closure oracle is owned by -`proofkit.supply-chain-quality.cli-output-witness-contract` at -`internal/app/cli_output_witness_contract_test.go`, selector -`TestRootDistinctOutputWitnessBindingsAreExact`; it is intentionally separate -from the general CLI topology corpus. Every row has direction `output`. The -selector/source oracle rejects -missing, surplus, or substituted native source paths independently of generic -digest freshness, including conversion between incomplete `nativeSource` and -`nativeSources` forms. - -These corrections do not add commands, evidence classes, provider claims, or -general-purpose abstractions. They are overturned only by an owner-approved -contract that removes the corresponding identity, ABI, executability, SemVer, -or wire-consumer invariant. - -### C-40 decision record - -```text -problem: - Exact proof bindings could silently lose a critical anti-vacuity selector, - and safe quoted README words could be executed as different argv. -chosen owner boundary: - REQ-PROOFKIT-QUALITY-010 owns selector-inventory admission; - REQ-PROOFKIT-SPEC-001 and REQ-PROOFKIT-QUALITY-019 own the installed README - command-to-current-product trace. -rejected lower-cost alternative: - Documentation-only quoting restrictions leave an executable false reject; - checking only selector existence leaves removal of a valid critical - selector invisible. -proof invariant: - Both critical scenarios equal their closed selector sets, and every admitted - README literal word maps to the same byte argument under bounded Bash - quoting and direct execution. -non-claims: - This is not a complete shell or Markdown parser and admits no expansion, - substitution, control operator, glob, multiline command, or arbitrary npm - command. -rollback or overturn condition: - An owner-approved structured argv field supersedes the README command line, - or the owning quality requirements remove the exact anti-vacuity scenarios. -why this avoids accidental complexity: - It extends one existing map and adds one boundary-local lexer with no - dependency, process invocation, expansion, or reusable parsing layer. -why this avoids premature over-decomposition: - The lexer has one consumer and remains adjacent to that trust boundary; - extraction into a package would invent ownership without a second consumer. -``` - -C-41 retains the C-40 owner boundary, non-claims, rollback condition, and -complexity analysis. Its proof invariant strengthens set equality to exact -triple equality: requirement owner, scenario identity, and selector set. -C-42 retains those boundaries and makes the planned closeout threshold equal -to the design's actual retirement predicate. -C-43 remains inside the same README extraction anti-corruption boundary and -adds no general shell, JSON, or Markdown parsing claim. -C-44 narrows preprocessing further; it adds no parser state or abstraction. -C-45 changes only oracle routing and no production behavior. -C-46 remains inside the bounded lexer and does not add general history -expansion. C-47 changes only closeout policy. C-48 removes repeated work without -weakening the positive integration proof. -C-49 makes the lexer linear in command bytes and adds no new accepted syntax. -C-50 names two already distinct invariants and creates no reusable package or -public abstraction. -C-51 changes only durable proof routing and its anti-deletion oracle. -C-52 leaves tests owned by QUALITY-022, QUALITY-025, and unrelated workflow -requirements in their existing scenarios; it does not claim every test in the -shared file belongs to QUALITY-013. -C-53 changes no readiness runtime behavior: it makes the existing intentional -change discoverable to consumers and aligns selector admission with the Go -toolchain. The release record remains the existing closed owner, and the -unnamed-parameter regression remains inside the existing coverage validator. -C-54 changes no public runtime semantics beyond fixing the confirmed -readiness false green. It adds proof for an already reachable browser state and -documentation for already implemented public changes; it creates no new -parser, UI state, command, or release evidence class. -C-55 changes only the copyable presentation of an existing help route and -binds the installed witness to those displayed bytes. C-56 narrows -preprocessing to Bash-compatible indentation and adds no accepted syntax, -command, resolver, or shell abstraction. -C-57 extends that same route-byte invariant through already existing commands -and the already shipped first-input example; it adds no command or product -policy. C-58 changes only consumer disclosure and migration accuracy. -C-59 closes existing workflow source owners rather than inventing another -evidence class. C-60 follows executable owner boundaries: the two scenarios -that share one exact selector and one single-path binding remain together, -while independently changing peripheral owners move without selector renames. -C-61 extends the existing closed step record without adding a workflow model. -C-62 strengthens the existing selector inventory with its already authoritative -path. C-63 changes only candidate admission, and C-64 changes only exact -measurement evidence. C-65 restores selective routing for existing scanner -requirements without creating a new requirement class. C-66 applies the -existing installed-route invariant uniformly to every leaf; it adds no command, -resolution mechanism, or public input behavior. -C-67 changes no candidate membership or repository bytes; it removes an -execution-environment contradiction from the existing candidate-admission -proof and adds no script or reusable abstraction. -C-68 narrows existing source permission claims to exact checked maps and adds -no provider authority. C-69 changes only installed-help admission and no -command dispatch, JSON shape, exit code, or runtime input semantics. -C-70 changes only durable proof routing and requirement precision. -C-71 changes only the executable final-closeout predicate and adds no provider -claim. -C-72 changes only the public root-variant selection contract and two formerly -ambiguous repeated-flag inputs; it adds no runtime schema validator, general -condition language, command, evidence class, or provider authority. -C-73 changes no runtime or public contract. It repairs only candidate -admission after the C-72 owner split. Keeping the cohesive pure condition -algorithm separate is lower-cost than returning its grammar and disjointness -logic to the generator I/O owner, while splitting that algorithm further would -be premature over-decomposition. -C-74 changes only one formerly ambiguous empty-valued invocation. Reusing the -existing parser error path and ABI corpus is lower-cost than adding a second -normalization layer or another condition state. -C-75 changes no repository contract. It keeps current worktree state and -baseline-relative candidate identity as distinct evidence classes instead of -adding a stateful staging abstraction. -C-76 changes no accepted option or public package surface. Two clone-returning -accessors in an `internal` package remove duplicated test authority and remain -cohesive with the native option owner. -C-77 removes an unproven extension point rather than adding a framework. A -future second owner can overturn the one-definition admission only with the -proof whose absence currently justifies rejection. -C-78 changes no code or contract. It separates whole-candidate identity from -one semantic owner subset using the existing in-memory staging predicates. -C-79 and C-80 add no product behavior: they close existing ABI and generator -proof paths. C-81 removes redundant volatile evidence instead of adding -another measurement owner. -C-82 strengthens only test anti-vacuity and introduces no new abstraction or -runtime branch. -C-83 adds only observed-output and exact-inventory conjuncts to the same ABI -oracle; it changes no command behavior. -C-84 reduces the implementation by one identical helper and does not change -condition ordering or package ownership. -C-85 changes only selective proof routing and its anti-deletion oracle; it -adds no package behavior, platform claim, test implementation, or evidence -class. -C-86 adds one boundary-local projection test and otherwise reuses existing -tests and validator machinery. It separates launcher and cleanup semantics, -changes no runtime behavior, and avoids a generic coverage-threshold framework -whose line coverage would not prove semantic adequacy. -C-87 changes only proof routing and reuses an existing semantic test; it adds -no runtime branch, file, abstraction, or release evidence class. -C-88 strengthens the existing scanner source oracle with one set-cardinality -conjunct and one mutation case; it adds no workflow model, runtime dependency, -provider claim, or production behavior. -C-89 adds one value renderer because three existing generated-command producers -and two installed channels need the same quoting invariant. It preserves -caller-owned display text, admits launcher identity once, adds no ambient -resolver or runner autodetection, and records the supported-channel public -string change plus migration. The wrapper fields are an anti-corruption -boundary, not a public command or package-manager framework. -C-90 changes source-checkout proof routing, pilot native-source closure, and -anti-substitution oracles only. Its focused self-check witness reuses the -existing contract-derived root assertion. It adds no public runtime schema -field, output-root change, consumer migration, parser, or general source-level -test analyzer. -C-91 adds one bounded value-admission conjunct at the existing launcher -boundary and reuses the shared redaction corpus; it adds no credential model or -secret scanner. -C-92 extends the existing renderer and exact inventory instead of introducing -a second route model. It executes admitted argv directly, retains caller-owned -argv, changes no public JSON field, and records the already-breaking display -and argv prefix migration without adding a shell parser or ambient resolver. -C-93 changes only a negative-test fixture: one pre-existing regular file with -the same content and exact mode provides a cross-platform distinct identity -without adding production synchronization or weakening the writer's mode and -content checks. -C-94 re-expresses the same equality theorem through Playwright's retryable web -assertions. It removes both page-realm callbacks, adds no retry or timeout -policy, does not split the end-to-end flow, and preserves every negative case -implied by exact ordered equality. -C-95 changes only snapshot evidence and prevents explanatory measurements from -outliving the bytes they describe. -C-96 closes an existing source grammar before lossy decoding. It adds no -GitHub Actions schema framework, recursively models no dynamic `on`, `env`, -`with`, or permission vocabulary, and changes no workflow bytes. -C-97 changes only language reachability in the existing source scanner. It -adds no MIME inference or binary classifier and leaves the token predicate -byte-for-byte unchanged. -C-98 changes only how the browser test performs selection. The exact emoji -range retains a nonzero expected start, a two-code-unit DOM span, and a -one-code-point output span, while click collapse preserves the same visible -authority transitions. No test-only production surface or browser-specific -branch is admitted. -C-99 changes temporary closeout authority only. It keeps the original audit -baseline as historical evidence while preventing it from impersonating the -current PR base. The two phases reflect observable state: the old PR base is -not required to equal the new integration base until the new head is pushed; -no repository product behavior or provider result is changed. -C-100 changes one exact artifact predicate after regeneration and introduces -no inferred coverage implication. -C-101 adds one Unicode general-category conjunct at the existing launcher -admission boundary. It adds no normalization, credential model, shell parser, -or alternate display renderer. Python-module paths containing `Cf` change from -accepted to rejected and are therefore declared in the breaking change record. -C-102 preserves all previously valid single-selector pilot routes and the -documented alias. It removes only ambiguous argv sequences, extends the -existing root-shape condition owner, and adds no new command, mode, or output -shape. The repeated-selector rejection is declared as a breaking compatibility -change with an exact migration step. -C-103 strengthens only the existing source-checkout release-declaration -witness and its durable owner projection. It adds no runtime branch, release -record field, second registry, or inferred source-diff completeness claim. -C-104 corrects one temporary execution-owner command and adds no runtime, -public-contract, package, workflow, or business-logic behavior. Its proof is -the exact absence of unbraced variable-colon forms plus successful publication -under the pre-existing remote lease. -C-105 corrects only the temporary publication epoch identity. It adds no -product, provider, branch-protection, or merge authority and does not weaken -the existing exact lease. -C-106 updates only candidate-staging and inventory evidence after owner files -already admitted by earlier corrections. It adds no source file, runtime -behavior, package contract, or new decomposition boundary. -C-107 strengthens one existing QUALITY-007 test predicate without changing -workflow bytes, permissions, or publication behavior. -C-108 corrects temporary plan parity evidence and changes no requirement, -binding, or product behavior. -C-109 removes stale explanatory measurements and changes no decomposition -boundary or source behavior. -C-110 narrows only the QUALITY-007 test oracle to the exact decoded type and -changes no workflow input or runtime behavior. -C-111 corrects temporal language in both review history and temporary closeout -authority and changes no staging operation beyond the already reviewed -three-file set. -C-112 closes the Scorecard-action subset inside the existing QUALITY-007 test -owner and adds no full step inventory, workflow byte, or publication behavior. -C-113 aligns the same bounded classifier with provider repository identity and -adds no general action parser, network dependency, or ref normalization. -C-114 narrows that classifier to its declared ASCII domain and adds no -normalization, Unicode mapping, or provider lookup. -C-115 removes a source-only wrapper dependency and replaces two wrapper audits -per state with one direct call to the already pinned `axe-core` distribution. -Let `D` be the default-enabled axe rules and `t` be default-disabled -`target-size`. Let exact options `O` omit `runOnly`, omit overrides for every -`r in D`, and contain only `rules.target-size.enabled = true`. Axe's selected -rule set under `O` is then `D union {t}` while the check options for each -`r in D` remain unchanged. The old admitted predicate was -`Violations(D) = empty and Violations({t}) = empty and Applicable(t) and -Incomplete(t) = empty`. The new admitted predicate over the one combined -result `R = axe.run(document, O)` is -`R.violations = empty and Applicable(R, t) and Incomplete(R, t) = empty`. -These predicates are equivalent at the per-rule outcome projection because -only `t` changes from unselected to selected; no equality of complete result -objects is claimed. A combined negative fixture must defeat a -`runOnly: target-size` mutant by requiring both a `target-size` violation on an -undersized named control and the default `button-name` violation on a -normal-sized unnamed button in the same result. - -The topology oracle must admit `O` by exact closed equality: the top level has -only `rules`; `rules` has only `target-size`; and that record has only the -literal boolean `enabled: true`. Missing, wrong, or surplus keys or values, -including `runOnly: ["target-size", "button-name"]` and any default-rule -override, must fail even though the two-defect fixture could still observe both -named violations. - -Inside the direct evaluation, capture `axe`, require the exact pre-run version -and callable `configure` and `run`, apply the wrapper-equivalent closed -configuration `allowedOrigins = [""]` and -`branding.application = "playwright"`, and only then run. Full result-byte, -timestamp, array-order, tool-option, help-URL, and two-run-to-one-run equality -remain explicit non-claims; QUALITY-022 admits only the pinned engine identity -and accessibility verdict projections named above. Isolated falsifiers must -prove: zero frames and any child frame reject before evaluation; -`page.frames()` equals exactly `[page.mainFrame()]` on admission; an absent or -wrong pre-run version invokes neither `configure` nor `run`; missing -`configure` or `run` fails closed; missing, wrong, or surplus configuration -keys fail the closed topology oracle; an absent or wrong returned -`testEngine.name` or `testEngine.version` rejects unless the pair is exactly -`("axe-core", axeDistributionVersion)`; and each audit performs exactly one -page evaluation and never creates or accesses a temporary page. - -The matrix and combined negative control use one dedicated Playwright fixture -that owns a fresh `Page` and its `BrowserContext` for one test state. The -fixture initializes exactly once before the test body and its teardown admits -the test only if that page completed exactly one audit. The harness rejects a -second initialization before another registration and a second audit before -another evaluation, including concurrent attempts; a failed init registration -releases ownership for one retry, while an audit attempt is fail-closed under -the existing zero-retry policy. Reusing one `Page` lifetime for multiple -audited states is not admitted by C-115 and requires a separately owned fixture -topology rather than a caller-invented state token. - -The exact QUALITY-022 runtime and topology owners are -`tests/browser/axe-harness.mjs`, `tests/browser/workspace.spec.mjs`, and -`scripts/browser-proof-inputs.test.mjs`. The exact source-only dependency -inventory owners are `package.json`, `package-lock.json`, -`internal/tools/packageverify/main.go`, and -`internal/tools/packageverify/main_test.go`. The durable statement is updated -only in `docs/specs/proofkit-supply-chain-quality/requirements.v1.json` and its -human projection -`docs/specs/proofkit-supply-chain-quality/overview.md`. Existing Proofkit -binding paths and witness identities remain unchanged; no new proof-like owner -is introduced. - -The single-main-frame precondition makes removal of the wrapper's recursive -frame topology explicit and fail-closed. A future child frame overturns this -design and requires a separately owned iframe-aware witness. Increasing the -timeout, adding retries, skipping Firefox, or upgrading the browser toolchain -without a causal falsifier are rejected because none proves bounded -first-attempt execution. Removing only the observed version probe is the -lower-byte alternative, but it is rejected because prior exact traces moved -the stall to other operations and the wrapper would retain all other avoidable -page-realm calls and temporary pages. - -C-115 is a controlled topology hypothesis, not a proven Firefox root-cause -repair. If a bounded repeated full Firefox project or the exact-head provider -attempt 1 again times out on any Playwright page, locator, or evaluation call, -the hypothesis is falsified and closeout remains blocked. Re-adjudicate the -retained trace before choosing another repair. Only if the new evidence again -localizes the failure to cross-operation engine-level liveness should a pinned -Playwright 1.62 toolchain and browser-lifetime isolation be tested as separate -candidate A/B epochs. Do not combine those changes with C-115 before this -falsifier, because that would erase causal attribution. No C-115 evidence -claims a product or UX defect, a specific browser crash, or the exact -Playwright/Firefox mechanism. - -C-116 is the required adjudication of that overturn condition. Let `F(i)` mean -that every test in independent Firefox process `i` terminates successfully -within the unchanged per-test bound. The first epoch observed -`F(1) through F(14)` and `not F(15)`. Therefore the universal claim -`for every i in 1..30, F(i)` is false; the fourteen passes remain diagnostic -evidence but cannot admit the candidate. The retained trace narrows the failed -boundary: `Locator.boundingBox()` began about 1.5 seconds into the test, its -internal locator resolved the visible SVG in about 3.5 milliseconds, and no -successful return was recorded before the deadline. The trace does not -distinguish the later Firefox content-quad response from handle disposal, so -the exact Firefox or Juggler cause remains a non-claim. The preserved page and -failure screenshot show a rendered graph, so no product or UX failure follows -from this tooling-liveness counterexample. The retained trace SHA-256 is -`a08498b0ce74c39a714856c855d7757f0907a8a67df3d06b1cdab3b652904fc7`; -the machine report SHA-256 is -`2e5475d20b3161d6d0128b9097288302ad70a94de57b90c6e569533c114383a9`. - -The old graph smoke test also lacked a useful raster theorem. A transparent -SVG can be Playwright-visible and produce a PNG larger than 1,000 bytes, while -an explicit one-pixel height can preserve its viewBox, computed minimum width, -and non-`none` stroke. Thus PNG size does not imply meaningful visible pixels, -and viewBox plus visibility does not imply the prior physical-height floor. -C-116 does not infer a broader raster theorem. It admits one bounded local SVG -structure and computed-style contract under the current owned viewport. - -Before activating Traceability, the witness arms an exact same-origin POST -route and request/response observers for `/api/v1/graph`; it does not issue an -independent API request. The route fetches the real upstream response, requires -at least one node, replaces only the first node label with one safe -deterministic sentinel proven absent from the upstream projection, and fulfills -that one app-issued request. The witness then activates the view, admits the -successful response linked to that request, deep-copies and freezes its -projection records, waits for the stable graph, and requires exactly one -matching request, route interception, and response over that window. The -expected node/edge order, count, labels, and endpoint relations come from that -single observed response, never from DOM datasets. The witness separately -equates those records with the graph dataset, the ordered SVG groups and lines, -and both ordered tables, and requires the sentinel in both the first node's -full title and truncated visible text. This rejects a renderer that -consistently omits a record from every derived DOM surface, compares against a -different response, or renders an unchanged fixture-equal cache. The one-field -intervention proves dependency on this response channel; it does not prove -independent causal consumption of every response field. - -The retryable style contract requires both `.graph-viewport` and its direct -SVG child to be visible with their exact current `display`, `visibility`, -`opacity`, transform-family, `filter`, `clip-path`, and `mask-image` values. The -closed transform family is `transform`, `translate`, `rotate`, `scale`, and -`offset-path`, each with its exact current `none` value. The viewport must -retain exact computed `overflow-x: auto` and `overflow-y: auto`; an isolated -`hidden` or `clip` mutant must fail. Every element on the exact owned chain -`html > body > main > #workspace-content > .graph-viewport`, the direct SVG, -and every admitted node group, rectangle, text, and edge must have computed -`animation-name: none`, `transition-duration: 0s`, and -`transition-delay: 0s`. The viewport, SVG, and admitted graph elements also -have computed `zoom: 1` and `content-visibility: visible`; every graph element -retains exact `filter: none`, `clip-path: none`, and `mask-image: none`. -Isolated zoom, content-visibility, positive-duration, zero-duration -positive-delay, local phase-split animation, and ancestor inherited-paint -phase-split animation mutants must fail. Closing CSS animation and both -transition time dimensions on the owned ancestor chain prevents retrying -property assertions from admitting either confirmed alternating CSS mutant; -simultaneous stability under script-driven mutation or the Web Animations API -remains an explicit non-claim. The SVG requires its `800px` computed minimum -width and a computed height matched -by the explicit numeric CSS serialization for values at least 180 pixels. It -requires every node group, rectangle, and text label to have non-`none` local -display, visible visibility, opacity equal to one, and the owned positive -opaque fill/stroke where applicable. Rectangle and text fill opacity and -rectangle stroke opacity must each equal one; isolated zero-value mutants for -all three properties must fail. Each node also has exactly one accessible -title with the response-derived label. For response-order index `i`, the -independent formula computes `x = 28 + (i mod 2) * 390` and -`y = 28 + floor(i / 2) * 76`; the direct children must be exactly -`title, rect, text`, the rectangle must have exact -`x, y, width = 350, height = 48, rx = 4`, the text must have exact -`x + 10, y + 29`, and the group, rectangle, and text must satisfy the complete -closed transform family above. An isolated swap of rectangle and text must -fail while preserving their membership, attributes, and content. Computed -rectangle `x`, `y`, `width`, `height`, `rx`, and `ry` must equal the current -exact pixel serializations, with `ry = auto`, so CSS geometry cannot override -exact attributes. Text must omit -`dx`, `dy`, `textLength`, `lengthAdjust`, and attribute-level `rotate`. Its -visible content is exactly `evidencePlane + ": " + label`, truncated only -when longer than 48 Unicode code points to the first 47 code points plus -`"..."`. Its computed text contract is exactly `font-size: 13px`, -`font-size-adjust: none`, `text-anchor: start`, `direction: ltr`, -`writing-mode: horizontal-tb`, `dominant-baseline: auto`, -`alignment-baseline` in the complete pinned set `auto` or `baseline`, -`letter-spacing: normal`, `word-spacing: 0px`, `text-transform: none`, and -`baseline-shift` in the complete pinned -serialization set `0px` or `baseline`. Independent zero-size, -geometry-override, individual-translate, motion-path, text-offset, empty-label, -zero-font-size, zero-font-adjustment, end-anchor, right-to-left direction, -vertical-writing, hanging dominant baseline, shifted alignment baseline, large -baseline shift, extreme-letter-spacing, extreme-word-spacing, and uppercase -transformation mutants must fail. Computed `-webkit-text-security` must equal -`none`, and an isolated `disc` mutant must fail. The text and title elements -must have no direct element children. Separate hidden-`tspan` mutants under -text and title must each fail while preserving the exact direct text bytes. -Generic -`toBeVisible()` and `toBeInViewport()` are rejected as replacement oracles: -for each matcher at least one harmful mutant/engine pair survives, while the -viewport matcher also rejects unscrolled baseline rows and would add a new -scroll operation. Neither is therefore one uniform cross-engine oracle. The -positive display predicate admits the pinned engines' `inline` or `block` -serialization but rejects `none`. Every edge is bound to independently -computed, response-derived non-degenerate endpoint coordinates and must have -non-`none` local display, visible visibility, opacity and stroke opacity equal -to one, a positive opaque computed stroke, `1.5px` stroke width, -`stroke-dasharray: none`, and the same closed transform family. Every line and -rectangle must have zero direct element children. The SVG root's direct element -children must equal exactly all response-derived lines in edge order followed -by all response-derived groups in node order, with no surplus. Separate inert -line-child, inert rectangle-child, and root-surplus element mutants plus a -zero-dash mutant must fail while preserving geometry and paint. A separate -root-order mutant moves the -first group before the last line while preserving both subsets' relative -order, identities, and counts. The complete document must contain zero SVG -declarative animation elements from the exact set `animate`, `animateColor`, -`animateMotion`, `animateTransform`, `discard`, and `set`. Separate hidden -external-SVG mutants must target a graph line coordinate and the existing -`#workspace-content` ancestor color; both must fail at this document boundary. -This direct source exclusion replaces local `id` and `xml:id` bans: the former -did not close ancestor targets and the latter did not create an addressable -target in any pinned engine. It intentionally does not use `toBeVisible()` on -an SVG line: horizontal or vertical zero-thickness line boxes are reported -differently by the pinned engines. The property `clip-path: none` is exact; -normal SVG viewport overflow clipping is neither rejected nor generalized into -a no-clipping claim. No production CSS floor is added: the used-height -assertion directly rejects the one-pixel mutant at the lower owner cost. - -The two graph table viewports, their tables, captions, section groups, rows, -headers, and response-derived cells form the visible trust-state projection. -Their captions are exactly `Admitted traceability nodes` and -`Admitted traceability edges`. Their ordered headers are exactly the two -inventories constructed by `workspace.js`; their exact ordered body-cell text -comes from the same frozen response records, not from the DOM. Exact visible -header, row, and cell counts close omission and surplus. Independent caption -substitution or emptying, header substitution or surplus, body-row reorder, -omission or surplus, and body-cell substitution mutants must fail. - -Each local table element must satisfy the retryable visible predicate, -computed opacity `1`, `filter: none`, `clip-path: none`, `mask-image: none`, -`content-visibility: visible`, `zoom: 1`, `animation-name: none`, -`transition-duration: 0s`, and `transition-delay: 0s`. Each text-bearing -caption, header, and body cell also requires the current exact positive opaque -computed color `rgb(23, 32, 51)`, positive `16px` font size, -`font-size-adjust: none`, `-webkit-text-security: none`, and -`text-transform: none`. Each table's exact response-derived row and cell -counts must equal the count of matching visible locators. This closes hidden -ancestors, hidden individual rows or cells, local alpha and glyph loss, local -effects, and phase-split table animation without adding raw page evaluation. -Separate -hidden-viewport, hidden-row, opacity-zero-cell, transparent header and data -cell color, filter-opacity, complete-clip, transparent-mask, zero-font-size, -zero-font-adjustment, text-security, text-transform, zero-zoom, -hidden-content-visibility, phase-split-cell animation, positive-duration, and -zero-duration positive-delay mutants must fail. Separate header reorder and -body-cell reorder, body-cell-only omission, and body-cell-only surplus mutants -must preserve every unrelated count and order dimension and fail their sole -predicate. Exact initial page-viewport intersection, pixel layout, ancestor -effective compositing outside the owned table viewport, font rasterization, -and raster equality remain non-claims. - -`internal/testsupport/browserfixture/fixture.go` owns the raw graph fixture; -`internal/command/requirementgraph/requirementgraph.go` constructs normalized -records and identities; `internal/command/requirementgraph/output_admission.go` -admits them; `internal/command/requirementbrowser/workspace.go` owns -snapshot/session closure; and -`internal/command/requirementbrowser/http_handler.go` owns windowing and the -HTTP envelope. `internal/command/requirementbrowser/assets/workspace.js` owns -SVG construction and the geometry formula. The witness independently restates -that formula and computes viewBox height plus edge coordinates from the -response order and endpoint relations; it never reads expected geometry from -the DOM or calls production code. The unchanged -`internal/command/requirementbrowser/assets/workspace.css` owns style, and -`tests/browser/workspace.spec.mjs` is the runtime witness. Existing -`REQ-PROOFKIT-SPEC-021` and -`REQ-PROOFKIT-QUALITY-022` bindings already own visible traceability UX and -the same non-empty passed browser identities, so C-116 changes no requirement, -public API, packet, route, or business rule. Pixel-perfect raster equality, -anti-aliasing, effective compositing, ancestor occlusion, useful pixels, -exact screenshots, simultaneous stability under script-driven mutation or the -Web Animations API, a general ban on necessary page evaluation, independent -causal consumption of every response field, and the exact engine defect remain -non-claims. Increasing timeout or retries, retaining any of the three raw -pass-path operations, accepting 14 of 15, or merging a toolchain upgrade into -the same repair are rejected because they either preserve the counterexample -or destroy causal attribution. Any change in the resolved browser-runtime -input set creates a new input digest and restarts the full 30-process epoch; a -design-only byte change restarts design review but does not imply a new runtime -digest. The failed epoch cannot be resumed. - -A pinned Playwright 1.61.1 versus 1.62 or browser-lifetime A/B is diagnostic -and nonblocking if the fresh C-116 epoch passes all 30 processes. It becomes -blocking only if the replacement web-first path times out again or if an exact -engine or browser-lifetime cause is claimed. Running it before the lower-cost -raw pass-path operation-removal/replacement falsifier would mix variables -without closing the independent oracle defects. - -### Playwright 1.62 A/B decision record - -Problem: the immutable Playwright 1.61.1 C-116 epoch passed locally, but the -first provider execution later stalled inside a different raw Playwright page -operation. Therefore local bounded reliability does not imply provider -liveness: `L and not P` falsifies `L -> P`. - -Chosen owner boundary: update exactly the root manifest pin, lock resolution, -package-verifier exact dev-dependency admission, and its fixture from 1.61.1 to -1.62.0. These are the four existing owners of the same source-only browser -proof toolchain version. The root dev-dependency pin is an intentional, -nonbreaking public package-metadata change, while the lock records the -toolchain dependency's Node `>=20` engine floor. That floor governs source-only -development proof execution, not the shipped package runtime. The admission -oracle changes only to preserve exact metadata parity. No shipped CLI -JSON/exit-code behavior, product runtime, business rule, browser test, retry, -timeout, worker, or report-semantics byte changes. - -Rejected lower-cost alternatives: rerunning or republishing the failed 1.61.1 -object cannot distinguish an intermittent provider stall from a version -effect; increasing timeout or retries weakens the falsifier; deleting the -negative control removes coverage; and changing browser lifetime or another -raw operation in the same epoch mixes causal variables. The already completed -raw graph pass-path removal was the lower-cost prior intervention and did not -establish provider liveness. - -Proof invariant: the version change creates a new resolved browser-runtime -input digest. The candidate is admissible only after a new immutable -30-process Firefox epoch passes from iteration 1, two subsequent full browser -proofs pass, the composite and full gates pass, committed-object reviewers -approve, and the first fresh provider attempt passes every required check. -All Firefox projections must retain the historical 25-test identity digest, -one worker, zero retries, and 30-second per-test timeout. - -Non-claims: a successful 1.62 epoch does not prove the exact 1.61.1 engine -defect, universal browser liveness, registry publication, provider attestation, -production readiness, or a general absence of browser-runner defects. The A/B -does not preserve byte-identical package metadata, change the consuming -runtime Node floor, or claim compatibility for unsupported contributor -environments below the development toolchain's Node floor. - -Rollback or overturn condition: revert all four version-owner changes together -if 1.62 changes a verdict or test identity, fails package admission, fails any -immutable local gate, or introduces a security or platform incompatibility. -If the exact 1.62 provider attempt stalls again while other required jobs pass, -retain diagnostics and reject a version-only causal conclusion; the next -admissible intervention is a separately reviewed browser-lifetime or remaining -raw-operation experiment with a fresh input digest and complete epoch. A green -provider attempt admits the 1.62 candidate but does not establish that the -upgrade alone caused the difference. - -Accidental-complexity and decomposition argument: synchronizing four existing -owners is the minimum change that preserves exact package admission. A new -adapter, retry layer, test file, configuration surface, or version registry -would add an owner without a second independent consumer. Keeping the decision -in this temporary remediation authority avoids both a permanent one-off -abstraction and premature file decomposition. - -### C-117 setgid-mutant decision record - -Problem: the test assumed `chmod returned nil -> requested setgid mode exists`. -Darwin provides a counterexample when the file group is outside the caller's -groups: `chmod` returns nil but clears setgid. The writer then sees its expected -`0644` mode, so publication is correct and an expected writer error is a false -oracle conclusion. - -Chosen owner boundary: change only `internal/app/cli_abi_test.go`. Before the -setgid temporary object is created, normalize the test root to the caller's -effective group. After every mode mutation, require an immediate `Lstat` mode -equality before the barrier returns. This preserves independent permission, -setuid, setgid, and sticky-bit falsifiers. - -Rejected lower-cost alternative: rerunning under the default temp root hides -the environmental counterexample; skipping or deleting setgid removes an -independent security falsifier; accepting nil without a materialization -postcondition preserves the false implication; and changing the writer would -reject a state that did not actually change. - -Proof invariant: both default-group and foreign-group `TMPDIR` executions must -materialize the requested setgid mode and make the unchanged writer reject it -100 of 100 times. The complete full gate must then pass. Because this test file -is outside the resolved browser input set, its correction must leave the -Playwright 1.62 epoch digest unchanged. - -Non-claims: this correction does not assert Windows or Plan 9 support, change -output semantics, strengthen a caller privilege boundary, or prove arbitrary -filesystem special-mode behavior. - -Rollback or overturn condition: revert the test-only correction if group -normalization changes production bytes, fails on a supported Darwin or Linux -platform, or the explicit postcondition cannot materialize the mutant. Such a -counterexample requires a platform-specific test-fixture design, not a weaker -writer assertion. - -Accidental-complexity and decomposition argument: two local setup and -postcondition checks repair the existing oracle. A platform adapter, new test -file, production hook, or reusable abstraction has no second consumer and -would add unjustified ownership. - -### C-118 browser-state decision record - -Problem: the handoff panel and the active workspace view are independent -presentation regions, but a late handoff response writes the same global state -projection used by a newer view. - -Chosen owner boundary: change only -`internal/command/requirementbrowser/assets/workspace.js` and its existing -browser owner test. Capture the active view request identity when a handoff is -submitted and condition only the global state write on that identity. - -Rejected lower-cost alternatives: aborting a handoff on view navigation cannot -revoke a server-side terminal commit and could hide a valid submitted packet; -discarding the late packet conflates view navigation with handoff cancellation. - -Proof invariant: after a handoff begins, a newer Diff or Graph view remains the -global workspace state when the handoff succeeds or fails, while the handoff -region still reports its own terminal result. - -Non-claims: view navigation does not cancel or roll back a submitted handoff; -the packet region is not hidden; no new persistence behavior or -provider-liveness claim is introduced. - -Rollback or overturn condition: revert C-118 if the handoff packet is no longer -observable after a valid submission or if one-shot terminal semantics change. - -Accidental-complexity and decomposition argument: one captured scalar and one -conditional state write separate two existing lifecycles without a controller, -queue, or new state machine. The correction stays in the existing browser owner -and existing test file. - -### C-119 browser-navigation decision record - -Problem: provider attempt 1 for the C-118 candidate timed out twice in Firefox -while `page.goto("/")` waited for `load`. Both retained traces show every local -HTML, CSS, JavaScript, manifest, and requirements response completed -successfully in milliseconds, and both failure snapshots show the initialized -workspace. The failures occurred before test-specific behavior. Evidence is CI -run `30334467601`, job `90196309721`, artifact `8678666391`, report SHA-256 -`50e45237d4d41ad221cd9320a37ed3a96ca413af2caa4db790309976e38a533d`, -and trace SHA-256 values -`66cf9aef659dc4bf93ba97c192e9fb6d5cd63fa67e012df1acf862fa848bddd7` -and `8f817656fe74b2920c559014216a598a7d4409571a775b8a01c0f4ad5340bf6f`. -The first C-119 candidate replaced `load` with `domcontentloaded`; provider -attempt 1 then reproduced two fully rendered Firefox timeouts at that earlier -lifecycle event. That evidence is CI run `30335313301`, job `90198820640`, -artifact `8678963003`, report SHA-256 -`e1c495fd99d35a972ae9301b5c4799369409186a4c7445d1f720e9a9f0ba1c61`, -and trace SHA-256 values -`f03f9fbf5b5e15f1d5bf3b54a5c7543f04a3c8c64fdae834164571f76d74a853` -and `302cc38a72aa7b8399e5fa36f0de31e6f19900c4c412ebef2f41e047581451d4`. - -Chosen owner boundary: change only the existing browser witness. Route all -then-current workspace opens and reloads through owner-local helpers that wait -only for navigation commit, reject a missing or unsuccessful main-resource -response, and require the visible server-owned workspace heading through -Playwright's exact accessible-name matcher before returning. Keep the -`about:blank` axe negative control unchanged. C-121 supersedes the lifecycle -mechanism while preserving these response and semantic admission obligations. - -Rejected lower-cost alternatives: a rerun would not satisfy attempt-1 proof; -waiting for `domcontentloaded` was empirically falsified by the next provider -attempt; raising the 30-second timeout would hide rather than remove the -irrelevant lifecycle dependency; `networkidle` would add a discouraged -ambient-network heuristic; production changes cannot repair a test-runner -lifecycle signal after the application is already rendered. - -Proof invariant: browser tests admit the new main document through its -successful navigation response and exact visible server-owned heading, while -each existing state, accessibility, and API assertion still proves the -behavior it owns. Navigation HTTP failure remains terminal. - -Non-claims: navigation commit and the heading did not themselves prove module -completion, application behavior, visual correctness, API completion, -provider reliability, or a generally flake-free browser engine. No retry, -timeout increase, server change, or product behavior change is introduced. - -Rollback or overturn condition: replace this policy only if the application -requires a resource whose correctness is not covered by the existing semantic -assertions and whose completion needs a separately owned readiness signal. - -Accidental-complexity and decomposition argument: two small owner-local helpers -centralized one repeated navigation policy without a new module, fixture, -retry controller, production hook, or general navigation abstraction. - -### C-120 navigation-guard falsifier decision record - -Problem: C-119's ordinary success paths exercise navigation through the new -helpers but do not distinguish an implementation with the response or exact -accessible-name guard removed. Therefore `allSuccess -> guardRequired` is -false for the 81-test matrix, even though both guards own admission decisions. - -Chosen owner boundary: add three cases to the existing browser contract corpus. -Intercept the main document, preserve its original body, and return status 503 -for open and reload independently. In a third case, preserve successful -navigation and change only the server heading to -`browser.fixture.workspace drift`, which retains the weaker substring. Admit -the original 200 response, byte-changing substitution, and completed route -fulfillment out of band before accepting the helper rejection. - -Rejected lower-cost alternative: source inspection proves guard presence but -not executable dependence. One 503 case cannot prove both duplicated operation -paths. A heading-absence mutant would not distinguish exact from substring -matching. A new fixture, module, production hook, retry, or timeout increase -adds no proof. - -Proof invariant: for every pinned engine, deleting the shared HTTP-response -guard or weakening the accessible-name matcher makes its owning negative -control fail; both open and reload operation paths reject their independently -injected 503 response; a route-handler or fixture-precondition failure cannot -satisfy the heading oracle; the unchanged C-121 implementation passes 31 tests -per engine. Controlled Chromium rehearsal deletes the shared response guard -and changes the exact matcher to substring matching; the owning falsifiers -must fail because the helper resolves instead of rejecting. - -Non-claims: these falsifiers do not prove arbitrary navigation failures, -browser-engine liveness, remote networking, or document integrity beyond the -admitted response and exact heading. - -Rollback or overturn condition: consolidate the two operation cases only if a -single counterexample still proves both open and reload routing through the -shared response-admission owner. - -Accidental-complexity and decomposition argument: three declarative tests reuse -the existing helpers and route API. No production state, abstraction, fixture, -or file is introduced. - -### C-121 provider-falsified navigation-lifecycle decision record - -Problem: provider attempt 1 for exact source -`dcc824b31f858ab8fea5be683e5d81f12f039279` timed out in Firefox while -`page.goto(..., {waitUntil: "commit"})` remained pending. The retained trace -records the main document status 200, complete local asset and API responses, -and a fully initialized workspace screenshot. Thus -`response200 and renderedWorkspace -> gotoCommitResolved` is false. The exact -evidence is run `30337477288`, job `90205431977`, diagnostic artifact -`8679825289`, GitHub artifact digest -`sha256:274cb4bbe4ef2bcbb55f476ed21414287f5d1d80632f051304ea07d0c7e94cba`, -report SHA-256 -`da78d940df3e969252c612bd89e90818b4ee5154dd81d05d74761d928164f953`, -and trace SHA-256 -`3e75d3fffd297652d30cc3c7f17b1252f92214e43990f4830e6350291e145cb4`. -The provider checked out synthetic merge -`30e9f1892d95b5aa886a360f5b14f28372c15862`; the exact source revision and -tree identity remain the source-owned comparison surface. - -Chosen owner boundary: change only the existing browser witness. Admit the -configured local base URL before use. Capture its current main frame, arm one -exact-URL main-frame navigation-response waiter, then schedule location -assignment or reload from the page and require the exact trigger token. Admit -only a successful response and the exact visible server-owned heading. Abort -and consume the waiter if the trigger or later admission fails. The current -inventory is 27 open-helper calls, two reload-helper calls, and one -classifier-only reload trigger. - -Rejected lower-cost alternatives: rerun-only acceptance violates attempt-1 -proof; another lifecycle event preserves the falsified dependency; a longer -timeout or retry hides it; `networkidle` adds ambient-network authority; -unawaited navigation creates an unowned rejection; fetch plus `setContent` -does not execute browser navigation semantics; production readiness hooks or -dependency churn change a non-owner surface. - -Proof invariant: the response waiter exists before the timer-backed trigger; -the trigger synchronously returns one exact scheduling token; only an -exact-URL main-frame navigation response can satisfy the waiter; status 503 is -terminal; and the exact visible heading remains the semantic readiness oracle. -A pure classifier truth table admits the main-frame navigation and rejects a -same-URL main-frame fetch, same-URL child-frame navigation, and foreign URL. -The live same-URL 503 fetch decoy proves operation-level classifier wiring. -Open and reload 503 cases prove shared response admission, heading drift proves -exact semantic admission, and a static source oracle excludes every direct -workspace lifecycle wait except the isolated `about:blank` axe control. The -unchanged corpus must pass 31 tests in each pinned engine with zero retries. -Controlled Chromium rehearsals removed the URL, navigation, and frame clauses -independently; each corresponding truth-table row failed. Deleting the shared -response guard failed both 503 cases, and weakening exact heading admission -failed the substring-preserving drift case. - -Non-claims: the classifier does not prove child-frame readiness, arbitrary -navigation behavior, remote-network reliability, browser-engine liveness, or -product behavior beyond the downstream assertions. A response and heading do -not replace the existing state, accessibility, API, or mutation oracles. - -Rollback or overturn condition: replace the response-event policy only if -provider evidence falsifies it or a separately owned application readiness -contract offers a lower-cost deterministic signal. Any new direct lifecycle -wait must first falsify the static source oracle and receive an owner-specific -counterexample. - -Accidental-complexity and decomposition argument: one pure three-clause -classifier and one shared observer serve both existing open and reload helpers. -The truth table closes the only added classification branch. A new module, -fixture, retry state machine, production hook, or generic navigation framework -would add ownership without another consumer. - -### C-122 correction-inventory parity decision record - -Problem: before `c2315fd`, P12.2 first declared the current correction -inventory as design, plan, and workspace test, but its executable equality -predicate also required `scripts/browser-proof-inputs.test.mjs`. After that -four-path correction was amended, the next correction epoch changed only -design, plan, and workspace test. Thus both the pre-amend three-vs-four -contradiction and the post-amend claim that four remained current were false. - -Chosen owner boundary: time-index the four-path set to the `c2315fd` staging -epoch and make both current P12.2 surfaces consume the exact same three sorted -post-`c2315fd` paths. - -Rejected lower-cost alternative: retaining the static owner would make the -current executable predicate fail because that file is unchanged from HEAD. -Deleting it from the historical `c2315fd` account would omit a real C-121 -correction. - -Proof invariant: for the current epoch, P12.2 has one exact three-path -correction set, the worktree inventory equals it before staging, and the staged -inventory equals it after staging. Earlier sets are explicitly epoch-bound. - -Non-claims: this parity does not prove the separate baseline-relative -150-file diff or 21-file addition inventory; their existing predicates remain -independent. - -Rollback or overturn condition: change the list whenever a later correction -epoch changes an owner file or an amend absorbs one, and update the epoch -statement, declaration, and predicate in the same reviewed edit. - -Accidental-complexity and decomposition argument: one added list row removes -contradictory authority. A generator or second inventory file would increase -cost without a durable consumer. - -### C-123 base-URL admission falsifier decision record - -Problem: C-121 added raw base-URL rejection clauses, but all passing browser -calls supplied the configured valid URL. Therefore -`allBrowserTestsPass -> everyBaseURLClauseRequired` was false. - -Chosen owner boundary: extend the existing navigation-classifier test with one -positive configured-URL row and independent negative rows for non-string -input, protocol, hostname, port, username, password, path, query, and fragment. -Run this table before navigation so an admission failure cannot be hidden by a -later response or heading failure. - -Rejected lower-cost alternative: source inspection proves clause presence but -not behavioral dependence. A single malformed URL cannot distinguish the -clauses. A new test file, exported production helper, or general URL policy -adds no owner. - -Proof invariant: the exact configured `http://127.0.0.1:/` bytes -normalize to their own canonical URL; changing any admitted authority -dimension makes its corresponding row reject with the owner error. Username -and password have separate counterexamples, including an empty-username, -non-empty-password URL. Controlled Chromium rehearsal removed the root-path -clause and the path-drift row failed because the helper admitted its mutant. - -Non-claims: the table does not claim DNS confinement, remote URL admission, -network isolation outside the test server, or general URL validation. - -Rollback or overturn condition: broaden the URL domain only when the browser -server contract admits another origin and adds an owner-specific positive and -negative proof. - -Accidental-complexity and decomposition argument: one data table exercises the -existing pure helper in its sole runtime owner. It adds no abstraction, -fixture, browser count, production hook, or retry path. - -### C-124 trigger-and-cleanup falsifier decision record - -Problem: every real trigger returned the expected token, and the 503 and -heading failures occurred after the response waiter settled. Deleting token -admission and `controller.abort()` therefore left static 22/22 and runtime -93/93 green. The source guards did not imply behavioral dependence. - -Chosen owner boundary: in the existing navigation test, inject one page-shaped -object whose response waiter remains pending, rejects when its supplied abort -signal fires, and otherwise resolves shortly to a distinct unsuccessful -response. Return a wrong trigger token and require the exact token error, -observed signal abort, and observed invocation of the waiter's rejection -consumer. - -Rejected lower-cost alternative: source inspection is the false-green being -repaired. A real network stall adds timing and browser authority. Removing the -guards would allow the trigger/observer handshake to drift and leave a pending -waiter rejection unowned. - -Proof invariant: wrong token is terminal before response admission; every -failure aborts a still-pending response observation; and its rejection is -explicitly consumed before the original error escapes. If token admission is -deleted, the distinct fallback response error appears. If abort or consumption -is deleted, its exact observation remains false. The injected operation -requires the exact event order `waiter-armed`, `trigger-called`, -`waiter-aborted`, `waiter-consumed`, which also makes pre-arm ordering -executable. - -Non-claims: the injected object is not a general Playwright mock, does not -prove every AbortController behavior, and does not replace real-browser -response, heading, or decoy cases. - -Rollback or overturn condition: remove the seam only if the observer API no -longer creates a pending rejection or another owner provides equally -deterministic token and cleanup falsifiers. - -Accidental-complexity and decomposition argument: one local object exercises -the two failure-only branches of the existing helper. It adds no exported -helper, fixture, file, retry, production state, or test identity. - -The C-27/C-115 axe harness remains an anti-corruption boundary, not a product -layer. C-115 removes the wrapper, not the boundary: exact source initialization, -runtime version admission, rule selection, frame scope, and returned-result -identity stay centralized. Remove the harness only after those obligations -move to a lower-cost owner with equivalent mutation and browser-runtime proof. - -## Review acceptance criteria - -The design is ready for implementation only when independent reviewers agree: - -1. every accepted row has one owner, counterexample, repair invariant, and - non-claim; -2. every rejected row states why its conclusion does not follow; -3. no repair changes valid-input business semantics without an explicit - compatibility record; -4. no new shared abstraction lacks two real consumers; -5. no documentation-only repair substitutes for a runtime or proof defect; -6. every stable browser state enters the executable state matrix; -7. the implementation plan orders owner and contract changes before dependent - projections and full closeout. diff --git a/docs/implementation/audit-remediation-plan.md b/docs/implementation/audit-remediation-plan.md deleted file mode 100644 index 77d428a..0000000 --- a/docs/implementation/audit-remediation-plan.md +++ /dev/null @@ -1,4077 +0,0 @@ -# Audit Remediation Implementation Plan - -Status: C-124 implementation candidate; C-01 through C-124 -corrections are present; provider validation is invalidated by the C-124 -semantic delta; unaffected prior validations remain historical evidence. - -Owner: `proofkit`. - -Design input: -`docs/implementation/audit-remediation-design.md`. - -Target baseline: `3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64`. - -Branch: `fix/audit-remediation`. - -Review history: - -- cycle 1: three `REVISE` verdicts; corrections applied for owner-first - sequencing, the omitted SBOM tranche, artifact dependencies, committed-object - closeout, exact selector/workflow inventories, race falsifiers, onboarding - continuity, and accessibility oracles; -- cycle 2: three `REVISE` verdicts; corrections applied for exact requirement - ownership, artifact-derived SBOM edges, generated preset topology, - source-only witness classification, branch/PR commands, help continuity, - and route-bearing README code spans; -- cycle 3: three `REVISE` verdicts; corrections applied for the exact two-file - generated-artifact exception and mutation-free branch preflight; -- cycle 4: two `APPROVE`, one `REVISE`; corrections applied for pre-switch - target-ref identity and immutable validated-SHA publication binding; -- cycle 5: unanimous `APPROVE`; no P0-P2 plan contradiction remains. -- implementation cycle 1: proof/security, contract/release, and - UX/accessibility reviewers returned `APPROVE` after C-01 through C-20 and - static-analysis cleanup; the complete worktree `npm run check` passed. -- committed-candidate cycle 1: two `REVISE` findings added C-21 and C-22 for - aggregate job bypass-field absence and the three held per-view loading - states; focused correction review returned three `APPROVE` verdicts. -- committed-candidate cycle 2: two `APPROVE` verdicts and one `REVISE` added - C-23 for ignored workflow/job/step execution controls and partial - `continue-on-error` evaluation; exact environment entries, nullable scalar - key presence, required leaf jobs, and the aggregate were closed, and focused - correction review returned three `APPROVE` verdicts. -- exact-commit cycle 1: proof/security and UX/architecture returned `APPROVE`; - contract/release returned `REVISE` because the `0.2.0` machine change record - omitted the intentional `adoption-doctor` blocked-state and exit-code - migration; C-24 closed the machine record, migration, rendered-note - falsifier, and proof binding, and focused correction review returned three - `APPROVE` verdicts. -- exact-commit cycle 2: proof/security and UX/architecture returned `APPROVE`; - contract/release returned `REVISE` because the record and top-level-only - semantic test omitted the intentional non-enforced advisory rule transition - from `passed` to `skipped`; the first focused review exposed the same - undeclared transition outside the touched selection in `enforce-touched`. - C-25 declares the complete migration, strengthens both adjacent-rule oracles, - and binds them; the second focused correction review returned three - `APPROVE` verdicts. -- provider cycle 1: the exact candidate passed locally but two provider browser - attempts failed at the same 30-second Firefox cap on different tests while - Chromium and WebKit passed; the run lifecycle then deleted the promised - retained traces and the success-only upload skipped them. C-26 preserves - attempt-scoped failure diagnostics without creating or admitting passed - proof so the moving Firefox stall can be diagnosed rather than guessed; the - focused correction review returned three `APPROVE` verdicts after routing - both selectors exclusively to QUALITY-022. -- provider cycle 2: C-26 retained the next locally reproduced Firefox traces; - both failures terminate at the first default axe-source evaluate rather than - an application action or assertion. A minified-source candidate passed two - full runs but a controlled falsifier made both minified and default sources - stall under continuous trace screenshots; the default source completed 13 - two-audit cycles without tracing and with screenshot-free action, DOM, - network, and source tracing. A third full run falsified screenshot removal - alone at the same large builder evaluate. C-27 therefore combines - browser-context axe initialization, constant builder loaders, screenshot-free - traces, one bounded best-effort post-failure screenshot, zero retries, and - the existing timeout; the combination passed 30 consecutive two-audit - Firefox cycles and still requires repeated clean first-attempt runtime proof - before exact-object closeout. -- provider correction review: proof and UX returned `APPROVE`; contract - returned `CONDITIONAL APPROVE` after the exact package allowlist, - owner-to-binding non-claims, and deterministic constant-loader reachability - oracle were aligned. Two consecutive frozen-byte 72-test browser proofs and - direct proof verification passed without an intervening browser-input edit, - discharging the only remaining review condition before final - committed-object revalidation. -- final committed-object review: proof/security found that a descriptor-relative - rename could follow the admitted output parent after it moved outside the - repository between the last route check and publication. C-28 binds the - irreversible rename to full routes through the repository root while keeping - temporary-file cleanup on the pinned parent, and adds exact outside-root and - in-root pre-publication falsifiers. -- final correction review: all three reviewers rejected C-28 because - `os.Root.Rename` resolves mutable source and destination routes separately and - a replacement can substitute the visible temporary basename. C-29 stages - temporary bytes at the repository root, re-admits their identity and the - destination-parent route after the exact barrier, keeps publication and - cleanup root-confined, and records adversarial same-user namespace mutation - after final admission as the unavoidable cross-platform non-claim. -- C-29 correction review: the root-level candidate was rejected for - writable-child and nested-filesystem regressions, missing content admission, - an omitted compatible release-record entry, and PR-body checks that did not - machine-validate every promised closeout fact. C-30 restores pinned - same-parent staging and publication with identity-plus-mode-plus-content - admission; - C-31 closes the release projection; C-32 adds a canonical closeout-body - record. The next correction review found that permission-only mode checks - omitted special bits and that local artifacts were reread from mutable paths; - C-33 requires complete-mode equality and one private byte snapshot per local - evidence object. C-34 removes artifact fields not admitted by the exact local - predicates. C-35 rejects multi-document JSON substitution and admits the - exact browser-project inventory. The independent maximum-reasoning audit then - reproduced dynamically false CI-step and release-candidate conditions; C-36 - closes both workflow condition inventories. Its correction review then - replaced the macOS smoke with an Ubuntu no-op while preserving aggregate - success and reproduced vacuous negative selectors; C-37 binds exact CI check - names, runners, and platform-smoke execution and requires positive owner - admission inside bound falsifiers. The next review inserted a semantic shadow - step, passed a mixed-type runner list, reproduced missing positive - package-gate owners, and found a malformed closeout filter; C-38 closes the - ordered platform topology and package-script owner, exact scalar runners, - positive CI/release package-gate admission, and executable singleton filter. - The next review moved the shadow into the repository-local setup action and - added an empty `run` key beside `uses`; C-39 binds exact local-action bytes - and makes execution-key presence part of the exact step inventory. -- The C-39 correction review returned three independent `APPROVE` verdicts - after reproducing the nested local-action shadow and null, empty, whitespace, - and dual execution-key mutants; no P0-P2 finding remains. -- The next independent maximum-reasoning audit approved the exact C-39 commit - at 97/100 with no P0-P2 finding and confirmed two P3 hardening gaps. C-40 - closes exact QUALITY-011/013 selector-set immutability and bounded - expansion-free README shell-word parsing; all exact-object gates and reviews - must therefore be repeated. -- C-40 focused review reproduced owner-only transfer of a protected scenario - while its exact selector set remained unchanged. C-41 binds every protected - inventory to the exact requirement/scenario pair and adds the missing - owner-transfer falsifier. -- The same cycle reproduced all-selector deletion through an empty-selector - early return, NUL admission that direct execution cannot preserve, and a - stale P0-P2-only final-review threshold. C-42 closes all three contradictions. -- Continued focused review reproduced Unicode-trim drift in both command and - JSON fence bytes, escaped-NUL lookahead bypass, and double-quoted interactive - Bash history expansion. C-43 preserves exact boundary bytes and closes each - lexer mutant without widening the grammar. -- C-43 review then reproduced a trailing escaped-space/tab false reject caused - by symmetric delimiter trimming. C-44 restricts preprocessing to leading - delimiters and preserves both mutants. -- The first complete package-level run then exposed that the generic - missing-function mutant was shadowed by the new exact-set check. C-45 routes - it through an unprotected binding and keeps both failure classes reachable. -- Continued review reproduced even-backslash history-literal false rejects, - one remaining P0-P2-only preparation threshold, and repeated generic I/O in - every pure inventory mutant. C-46 through C-48 close all three without - widening product scope. -- Complexity review then found quadratic rescanning of long non-history - backslash runs in the first C-46 helper. C-49 consumes each run once and - retains a large-run regression. -- The next complete package-level run showed partial generic fixtures stopping - at the earlier global inventory phase. C-50 keeps production composition but - routes isolated fixtures directly to the validation phase they own. -- Frozen C50 review then found two generic executability falsifiers absent from - selective QUALITY-010 routing. C-51 binds both and closes the scenario's - exact four-selector inventory. -- Frozen C51 review reproduced the same gap for QUALITY-013's permission-floor - falsifier. A closed-world owner pass found ten omitted typed-oracle tests; - C-52 binds and protects the complete seventeen-selector owner surface. -- The frozen C52 correction review returned three independent `APPROVE` - verdicts with no confirmed P0-P3 finding after exact inventory, README - lexer, JSON byte, owner-separation, documentation-parity, and business-logic - checks. The implementation is therefore ready for final committed-object - validation. -- The next exact-commit review confirmed that readiness-closeout's intentional - character-reference verdict change was absent from the closed release record - and that selector admission rejected a toolchain-valid unnamed - `*testing.T` parameter. C-53 adds the release declaration, migration, - rendered-note witness, Go grammar regression, and exact QUALITY-010 and - QUALITY-024 selector inventories before committed-object validation repeats. -- The repeated exact-commit review confirmed five P2 gaps: decoding before - Markdown pipe parsing, an untested stable specifications no-match state, - undisclosed removal of synthetic Arrow-key navigation, and undisclosed - compatible pilot-all envelope plus witness-selector I/O additions. C-54 - repairs the parser order, adds the complete browser row, and projects all - three public changes through the closed release record and rendered notes. -- The next exact-commit UX review confirmed that root help displayed a bare - executable unavailable to a normal npm consumer while the installed witness - executed separately hard-coded argv. C-55 makes the displayed route the - copyable offline npm exec command, parses and executes those exact bytes, and - rejects the bare-route mutant. -- C-55 correction review confirmed that Unicode `TrimSpace` admitted leading - or trailing NBSP around the displayed command even though Bash does not - treat NBSP as an IFS delimiter. C-56 removes only the authored leading - ASCII space/tab indentation and keeps both NBSP falsifiers. -- The next exact-commit review confirmed a discontinuous onboarding witness, - incomplete and misclassified release declarations, under-scoped symlink - migration, merge/release semantic shadow steps, and a five-owner workflow - oracle concentration. C-57 through C-60 close the full displayed route, - release classification and migration, complete step inventories, exact - large-file ledger, owner-aligned split, and moved-selector anti-deletion - proof. -- The C-60 decomposition review approved one minimal owner-cluster split and - rejected one-requirement-per-file decomposition because QUALITY-011 and - QUALITY-013 share an exact selector while each binding admits one - `witnessPath`. The implementation preserves that cluster and separates only - independently changing owners plus neutral support. -- The C-60 correction review confirmed six residual gaps in step-field - closure, witness-path ownership, untracked-file staging, exact size evidence, - scanner selective routing, and ordinary leaf invocation UX. C-61 through - C-66 add independent mutants and owner-bound repairs before candidate - creation. -- Candidate-command rehearsal rejected temporary-file cleanup under the active - destructive-action guard. C-67 keeps the exact staging predicates but uses - in-memory inventories and stdin pathspec admission, so the documented - sequence is executable without cleanup authority. -- The next focused review admitted a missing scanner permission floor, a - surplus provider write scope, an installed block before Usage, and a - command-token prefix collision. C-68 and C-69 add exact permission-map and - post-parse invocation predicates with independent mutants. -- C-69 proof review then deleted the new test while every bound QUALITY-019 - selector remained green. C-70 adds the falsifier to the exact owner selector - and path inventory. -- The final C-61 through C-70 focused correction review returned three - independent `APPROVE` verdicts with no confirmed P0-P3 finding on one - unchanged frozen snapshot. Candidate staging and exact committed-object - validation may proceed. -- Final provider-closeout rehearsal then rejected the current 75-test browser - matrix because its executable predicate still required 24 rather than 25 - tests per project. C-71 aligns that predicate with the immutable gate output - before repeating the entire committed-object epoch. -- The repeated exact-commit architecture review then reproduced overlapping - adoption output conditions and showed that last-write-wins repeated - `--mode`/`--pilot` argv prevented a unique raw-argv selector projection. - C-72 adds the bounded condition model, finite native-option closure, - argv-derived ABI binding, duplicate-selector rejection, and release migration - before the committed-object epoch repeats again. -- Splitting the C-72 pure condition algorithm from generator I/O then created - a sixth decomposition owner while P12.2 still required the earlier exact - five-file workflow inventory. C-73 extends that owner subset to six without - weakening staged-path equality or the empty-remainder predicates. -- Raw-argv falsification then admitted `--pilot ""`: the parser marked the flag - present but native normalization and the ABI condition both treated its empty - value as omission. C-74 rejects the empty valued selector and projects that - intentional behavior through the exact ABI, requirements, release record, - and migration. -- Staging rehearsal then rejected comparison of that six-file - decomposition subset with the current untracked set: the earlier candidate - already owns the five workflow files, so the correction amend has exactly one - untracked condition-model owner. C-75 proves the current set independently - and retains the owner subset after staging. -- Condition-closure review then found that the 80-state test duplicated native - mode and pilot literals. C-76 derives immutable domains from the same - internal lists that build native admission maps and preserves the exact - 80-combination and twelve-valid-state assertions. -- Claim review then found that the generic condition grammar did not prove - native-domain or argv closure for an arbitrary future definition. C-77 - admits only the current adoption output owner and makes a second opt-in fail - until its own executable closure witness is added. -- Baseline-diff rehearsal then found seventeen pre-existing added paths, so the - six decomposition owners were not the complete baseline-relative inventory. - C-78 requires all eighteen final added paths exactly and proves the six-owner - set only as a subset. -- Independent C-78 review then found that one guidance failure emitted JSON - without exact condition/variant binding, the admitted definition could be - rebound through another command or direction, and explanatory prose retained - stale line counts. C-79 through C-81 close the JSON route, exact owner triple, - and snapshot-measurement ownership. -- C-79 correction review then deleted both guidance-failure route coordinates - while the duplicate successful guidance condition preserved the global count. - C-82 requires JSON assertion and both route coordinates together per case. -- C-82 review then deleted the JSON assertion with both coordinates while - runtime still emitted unchecked JSON. C-83 equates fixture expectation with - observed stdout and protects the exact fourteen-case JSON inventory. -- Final committed-object decomposition review then found an identical generic - sorted-key helper duplicated inside the same Go package. C-84 reuses the - existing owner and deletes the redundant helper and import. -- Final committed-object proof review then found that the critical Mach-O - byte-compatibility scenario selected only a README projection test. C-85 - binds the exact four byte-admission/parser witnesses and protects their - selector and path inventory. -- Repeated exact-object review then found two analogous semantic false routes: - Python wheel-platform parity did not reach wheel projection or verification, - and browser one-shot cleanup selected only the fixed launcher. C-86 binds - the named operations directly and closes both exact inventories. -- Exhaustive review of all candidate-added selector rows then found one final - analogous route: mutable-release-fact policy selected only reference closure. - C-87 binds its existing ten-case stale-fact test and exact inventory. -- The independent Sol/max audit then found a scanner authority escape outside - selector routing: exact permissions on named jobs did not reject an - unclassified write-capable job. C-88 closes the exact advisory/provider job - union and adds the surplus-job falsifier. -- Terminal UX review then executed a preset's exact generated continuation in - the installed npm consumer and reproduced a bare-binary `command not found`. - Initial review rejected a global npm renderer because it broke the Python - wheel. C-89 now uses an explicit immutable launcher profile and proves exact - continuations in both installed channels. -- Terminal contract review then proved that adoption, pilot, and self-check - output selectors could pass without observing their declared output roots, - and that pilot output omitted its app-layer union constructor from native - ownership. C-90 closes all three exact selector/binding/source tuples. -- Frozen implementation review then found secret/control launcher values and - unbound help, structured argv, project workflow identity, and installed-wheel - route sinks. C-91 hardens admission; C-92 closes and binds those sinks while - preserving caller-owned argv. -- Provider exact-object review then reproduced Linux inode reuse inside the - output-writer identity mutant and retained a Firefox trace that stalled only - in the graph table's page-realm `evaluateAll` after the page was fully - rendered. C-93 makes the identity substitution deterministic with a - pre-existing live replacement; C-94 proves the same exact ordered table - equality through retryable count and indexed-attribute assertions, retaining - zero retries and the 30-second timeout. Independent focused architecture and - UX reviews approved both repair classes. -- Final ledger review found the `agentroute.go` measurement stale after the - last static-analysis cleanup. C-95 requires one more complete threshold - measurement after all correction bytes freeze. -- The first exact-object Sol/max audit then reproduced two input-closure gaps: - typed workflow decoding ignored valid execution controls, and CSS was absent - from source hygiene. C-96 adds a raw closed-key oracle over the exact tracked - workflow inventory with only exact release-environment exceptions. C-97 adds - CSS and derives both staged and worktree language mutants from tracked - browser-asset extensions without changing the token matcher. -- Dependency pre-merge validation then produced two current-base Firefox - failures on one immutable dependency branch; the timeout moved between the - collapsed and Unicode selection scenarios. C-98 replaces their repeated - manual page-realm range synthesis with Playwright `selectText` and click for - collapse plus one locator-scoped exact-range operation for the nonzero - Unicode selection while preserving one worker, zero retries, 30 seconds, the - exact test identities, and Unicode code-point proof. -- The next exact-object Sol/max audit returned four P2 findings and no P0, P1, - or P3 finding. C-99 separates the historical audit baseline from the current - integration, PR, and provider base; C-100 admits the final 173-scenario - coverage artifact; C-101 closes launcher admission over Unicode `Cc` and - `Cf`; and C-102 declares the accepted contract-envelope stack alias while - rejecting repeated or mixed pilot selectors. -- C-99 through C-102 review cycle 1 returned one `APPROVE` and two `REVISE` - verdicts and closed exhaustive Unicode classification, two-phase identity, - singleton-parent, temporary-closeout, durable-owner, and compatibility - omissions. Cycle 2 returned one `APPROVE` and two `REVISE` verdicts because - its migration wording excluded the valid omitted default-first pilot route. - Cycle 3 returned three independent `APPROVE` verdicts on the same frozen diff - with no confirmed P1-P3 finding. -- The mandatory final Sol/max audit of exact commit - `e55bfc6e5641aed906d9a3c02e56a431bc0ca4b5` returned one P2 and no P0, P1, - or P3 finding: the current release-record witness checked only 10 of 12 - breaking changes and 7 of 11 additions and admitted semantic deletion or - surplus after regeneration. C-103 closes the entire record and rendered-note - inventories at the existing QUALITY-024 selector. -- C-103 review cycle 1 returned one `APPROVE` and two `REVISE` verdicts and - added exact machine ID/order mutants plus note-projection closure. Cycle 2 - returned two `APPROVE` verdicts and one `REVISE` because section-local - equality still admitted appended surplus, duplicate, or second owned - sections. Cycle 3 returned three independent `APPROVE` verdicts on one - frozen diff after complete ordered machine equality and one independently - authored byte-exact full-note projection closed every confirmed escape; no - confirmed P1-P3 finding remains. -- Publication rehearsal after the first C-103 terminal approval reproduced - the plan's unbraced zsh variable-colon refspec failure. C-104 uses an - unambiguous braced variable, requires a complete tracked-plan scan for the - rejected form, and repeats the exact-object and provider epoch. -- C-104 focused review returned two `APPROVE` verdicts and one `REVISE` - because the plan's lease still preceded the already successful first - publication. C-105 preserves the first lease as history and binds the next - correction publication to exact current remote head `90090a5c...`. -- The mandatory new post-C-105 Sol/max audit returned one P2 and no other - confirmed P0-P3 finding: P12.2 still declared 18 baseline-relative additions - after three later owner-test additions made the exact final set 21. C-106 - refreshes that closed set and the then-current C-106 two-file amend staging - predicate. -- The mandatory new post-C-106 exact-object Sol/max audit of - `4a828d1be9e3f9cab0e93d4ef5991fef0d2cd475` returned two P2 findings, one P3 - finding, and no other confirmed P0-P3 finding. C-107 closes the exact - Scorecard output-input set with a surplus mutant; C-108 restores the complete - 30-ID requirement-invariant delta; C-109 removes the four stale - reverse-decomposition line-count qualifiers. -- C-107 through C-109 focused review returned one `APPROVE` and two `REVISE` - verdicts. C-110 replaces generic truth normalization with exact decoded - boolean admission and adds the surviving string-substitution mutant. C-111 - time-indexes the historical two-file set to C-106 while preserving P12.2's - then-current three-file set. -- C-110 through C-111 focused review returned two `APPROVE` and one `REVISE` - verdict because one design-history sentence still called C-106 the latest - current epoch. C-111 now time-indexes every historical 21-path and two-file - assertion to the C-106 freeze. -- C-111 focused review cycle 3 returned two `APPROVE` and one `REVISE` because - a differently named second Scorecard action could bypass the selected-step - input predicate. C-112 closes the complete Scorecard-action subset and adds - the surviving second-action mutant. -- C-112 focused review cycle 4 returned three `REVISE` verdicts because the - Scorecard subset classifier was case-sensitive while GitHub - owner/repository identity is not. C-113 admits the repository portion with - case-insensitive equality, preserves ref bytes, and adds the surviving - mixed-case second-action mutant. -- C-113 focused review cycle 5 returned three `REVISE` verdicts because Unicode - simple case folding was broader than the declared ASCII repository domain. - C-114 rejects non-ASCII repository bytes and adds the surviving long-s - mutant. -- Exact provider run `30275221625` for - `81e2c7d570e1982ffe4a9f1e5a43150438017b41` passed source and macOS quality, - but `quality / browser runtime` passed only 74 of 75 tests and failed, which - caused `quality / required aggregate` to fail. The retained Firefox trace - stops at the auxiliary pre-audit version evaluation after the page state was - rendered; it does not prove the underlying Firefox/Juggler cause. -- C-115 design review required five correction cycles before cycle 6 returned - three independent `APPROVE` verdicts. The approved controlled hypothesis - removes the - source-only axe wrapper, combines the default and explicit target-size - projections in one direct pinned-engine run, closes rule/config/frame/result - and call-topology mutants, and reopens adjudication if a bounded full - Firefox or exact-provider attempt times out again. -- C-115 plan review cycles 1 through 6 returned at least one `REVISE` verdict - while closing the exact dependency-inventory selector, materialized and root - digest remeasurement, canonical Firefox identity parity, one-shell - lifecycle, bounded server termination, per-process wall-clock deadlines, - leader-versus-descendant process-group cleanup, pre-spawn wrapper-signal - forwarding, residual-process non-claims, and exact signal/probe forensic - records. Cycle 7 returned three independent `APPROVE` verdicts on SHA-256 - `bb8eeb2317018030fbb273e1f18e081c33e1a022c78223947b3a8f989e626d44` - with no confirmed plan gap. -- C-115 implementation review returned three independent `APPROVE` verdicts - on exact diff SHA-256 - `4bf170fc8f5ea50619bc414badd88cd0997198e0419e2739357760c0e577d33f`; - direct axe topology, dependency closure, rollback, concurrency, and - zero-retry mutants were green. -- The immutable C-115 epoch used input digest - `sha256:8099d7060ba9033c1e8317b6032a8776ef21c879b371edda9a460732f66281f4`. - Iterations 1 through 14 passed 25 of 25 Firefox tests. Iteration 15 passed - 24 of 25 and timed out in the graph test after its visible SVG locator - resolved inside `Locator.boundingBox()`. The epoch stopped; trace SHA-256 - `a08498b0ce74c39a714856c855d7757f0907a8a67df3d06b1cdab3b652904fc7` - and report SHA-256 - `2e5475d20b3161d6d0128b9097288302ad70a94de57b90c6e569533c114383a9` - are diagnostic evidence, not product-failure or exact-engine-cause proof. -- C-116 design review cycles 1 through 13 returned at least one `REVISE` - verdict while closing response causality, local SVG structure and paint, - graph-table trust-state visibility, CSS and SMIL temporal escapes, exact - falsifier independence, and cross-engine serialization. Cycle 14 returned - three independent `APPROVE` verdicts on git blob - `598dfb89b7567df269b41491b15c7fe527248b3d` with no confirmed P0-P3 - finding. -- C-116 plan review cycle 1 returned one `APPROVE` and two `REVISE` verdicts. - The correction makes route/observer teardown an awaited fail-closed state - machine, makes one exact assertion plan observable through an injectable - branch-free executor, and binds every new epoch to the historical 25-ID - digest derived from the hash-verified C-115 failure report. -- C-116 plan review cycle 2 returned one `REVISE`, one `APPROVE`, and one - invalidated review. The correction re-admits the historical 25-ID bytes and - digest after the second full browser run, composite browser gate, and final - full gate rather than checking only the first full projection. -- C-116 plan review cycle 3 returned three independent `APPROVE` verdicts on - git blob `e04c4bdd8859ac71b0d02fde06e7c025813ae459` with no confirmed - P0-P3 finding. C-116 is approved for implementation. -- The complete C-116 executable epoch passed on runtime candidate - `0c67de58b0b9837d714e417f64758a76368f3efa`. Its 30 separate Firefox - processes passed 750 of 750 tests with one worker, zero retries, zero - skipped, unexpected, or flaky tests, and one immutable input digest - `sha256:ec3d79218e20831e726bf45e171b1d0276fdf22a04790a13f1e72e6df8dbee0d`. - Every iteration retained historical test-ID digest - `sha256:f7b80cd6ea950cad6693a7b11020f746581d6eba4f2b7314700e4161448a554c`; - the records JSONL SHA-256 is - `e38754615878a012358d2fe75fd4af031107450a7ec2bc6d70db6bc89c543051`. - Both following full browser proofs passed 75 of 75, the composite browser - gate passed 21 static and 75 runtime tests, and the final `npm run check` - passed. The build, two full-browser, composite, and full-check watchdogs all - recorded `exited`, leader exit code zero, no leader signal, and empty signal - and process-group probe error sets. -- Provider run `30297044766`, attempt 1, reported head - `26e44b79a90b41494f9971b84f66e4b737bc9baa` but checked out synthetic merge - commit `da27a7a1b3e17a901a47621a31ca8ae3432f9901`; their trees are both - `ae3b0b16efc3d185425a91488b1f902eee630c2f`. It later timed out in the - Firefox focus negative control after Playwright 1.61.1 entered - `evalOnSelectorAll`. Artifact `8665124396` has GitHub digest - `sha256:db3179664637de3b053bde5efce6b0e2e8b44e3d96c5b7bf07032a270b2b46b5`; - its report, trace ZIP, and inner trace SHA-256 values are respectively - `3498361d22679cc87c6560c055750bb3c782bb1d8761b28e5287499e0486a4d2`, - `b4f5560b9e0e240dab35e631d9b848a6f18817b02ca6c03c2254a32ba989328d`, - and `86a30f0e21dc41a9961d26506f262e18a1cfd8832cca24d9c70a1504864dc0a4`. - The inner trace has no matching return. Chromium, WebKit, source, macOS - smoke, CodeQL, OSV, and semantic diff passed. This falsifies provider - liveness, not product behavior, and activates the plan's isolated 1.62 A/B. - Change only `package.json`, `package-lock.json`, the package-verifier exact - dev-dependency pin, and its fixture. Preserve all tests, one worker, zero - retries, 30-second tests, production code, and business logic. Require - package-verifier tests, then a new input digest and immutable 30-process - Firefox epoch from iteration 1, two full browser proofs, browser static 21 - of 21 plus runtime 75 of 75, the full gate, committed-object review, and a - fresh attempt-1 provider run. - Bot PR 80 run `30250528617` used base - `3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64`, synthetic merge - `3367101eaf48fd664f1c1975181c15d047d7fac2`, and browser artifact - `8646807702` with GitHub digest - `sha256:57de6b30f9a3a82ca33b4ad18f9f36c2b47dbc55dac301c4f11669040b7a4ae1`. - Its green provider browser job ran only the older six tests per engine, so it - is supporting A/B evidence only and does not cover the current focus negative - control. Its red source and aggregate jobs prove that its unsynchronized - verifier pin is not independently mergeable. -- The first Playwright 1.62 post-epoch full gate exposed C-117. With a retained - `wheel`-group `TMPDIR` outside the caller's groups, Darwin returned success - from the setgid `chmod` but cleared the bit. The writer correctly observed - unchanged `0644`; the test incorrectly treated API success as proof that its - mutant existed. The subtest passed 100 of 100 under the default - `staff`-group temp root and failed 100 of 100 under the retained root. Repair - the test oracle only; production output semantics remain unchanged. -- Final exact-object review found that C-119's HTTP-response and exact-heading - guards had no guard-specific falsifiers: deleting or weakening them retained - an 81-test green matrix. C-120 adds distinct open and reload 503 cases and a - substring-preserving accessible-name drift before a new committed-object and - provider epoch. -- Provider attempt 1 for exact source - `dcc824b31f858ab8fea5be683e5d81f12f039279` falsified even the remaining - `commit` lifecycle wait: Firefox received status 200, loaded every local - resource, and rendered the initialized workspace while `page.goto` remained - pending. C-121 replaces lifecycle waiting with a pre-armed exact-URL, - main-frame navigation-response observer, exact trigger token, response - admission, and the existing exact heading. A pure classifier truth table, - live same-URL fetch decoy, static lifecycle-source oracle, and the C-120 - response and heading mutants close the new proof surface. -- Final Sol/max review of exact candidate - `c2315fdf28be95eab08089008773d7dd234d9c96` reproduced two false-green - documentation/proof candidates. C-122 makes P12.2's declarative and - executable post-`c2315fd` correction inventories the same three paths. - C-123 adds positive - and negative exact raw-base-URL cases so each local-origin clause is - executable rather than source-only. -- The same review reproduced C-124 by deleting token admission and waiter - abort while static 22/22 and runtime 93/93 stayed green. One injected - pending waiter in the existing navigation test now requires the exact token - failure, abort observation, and rejection consumption independently. - -## Purpose - -This plan converts the approved audit-remediation design into an ordered, -test-first implementation graph. It is complete only when every finding has a -durable owner update, an executable counterexample, a passing repair, a -business-logic compatibility disposition, and final full-gate evidence. - -## Authority and retirement - -The design's authority order, scope, non-claims, and retirement rules apply -unchanged. This plan is temporary execution authority. It retires with the -design only after the durable implementation, exact-object gates and reviews, -validated branch and pull request, the design's closed source-owned required -provider-check inventory is present and successful, every other triggered -check has a terminal disposition, retrospective routing is complete, and the -final pull-request closeout projection satisfies the design's retirement -predicate. - -## Execution policy - -Implementation uses one writer in the shared worktree. Review agents remain -read-only until code-review rounds. This prevents shared-state mutation from -invalidating independent review evidence. - -For every tranche: - -```text -owner delta - -> current-wrong counterexample - -> observe expected red - -> minimal production repair - -> narrow green - -> adjacent positive regression - -> contract/binding/non-claim parity -``` - -No tranche may: - -- weaken a durable non-claim to make a test pass; -- infer provider or registry evidence from local artifacts; -- add a shared abstraction with fewer than two genuine consumers; -- split or merge a large file without a separately proven cohesion defect; -- hide an unresolved selective edge behind the full gate; -- retain both old and new release-record owners; -- modify an already published release or historical tag. - -The `owner delta` is not deferred to P10. Before the first counterexample in -each P1-P9 tranche, the implementer must update the exact requirement, -non-claim, binding skeleton, and witness selector named by the design's durable -proof table. The binding may point to a test that is red during the tranche, -but the owner and intended proof route must already exist. P10 only validates -cross-owner parity, canonical order, and final freshness. - -## Dependency graph - -```text -P0 frozen baseline and owner inventory - | - +--> P1 release/version foundation - | | - | +--> P1A artifact-specific SBOM semantics - | +--> P4 machine CLI contracts - | +--> P8 package/onboarding projections - | - +--> P2 evidence/channel classification - | - +--> P3 confined filesystem boundaries - | - +--> P5 workflow and release oracles - | - +--> P6 proof-gap falsifiers - | - +--> P7 context wire migration - | | - | +--> P9 browser state and accessibility - | - +--> P8 package/onboarding projections - | - +--> P9 browser state and accessibility - | - v - P10 durable proof surfaces - | - v - P11 worktree review and candidate preparation - | - v - P12 committed-object review, push, pull request, provider status -``` - -Only P1 must precede public contract and package metadata projections. P7 must -precede browser tests that consume the new manifest vocabulary. Other -production tranches may be developed independently in concept, but are applied -serially in the shared worktree. - -## P0: Freeze and preflight - -### Objective - -Prove the implementation starts from the reviewed object and preserve user -changes. - -### Actions - -1. Before any branch mutation, require the only changed paths to be the two - reviewed, untracked implementation documents: - - ```bash - git branch --show-current - git rev-parse HEAD - test "$(git status --porcelain=v1 --untracked-files=all)" = \ - $'?? docs/implementation/audit-remediation-design.md\n?? docs/implementation/audit-remediation-plan.md' - ``` - -2. Before any switch, reject a target branch attached to another worktree: - - ```bash - current_worktree="$(git rev-parse --show-toplevel)" - target_worktree="$(git worktree list --porcelain | awk ' - /^worktree / { worktree = substr($0, 10) } - $0 == "branch refs/heads/fix/audit-remediation" { print worktree } - ')" - test -z "$target_worktree" || test "$target_worktree" = "$current_worktree" - ``` - -3. If the target ref already exists, validate its object and base relation - read-only before switching: - - ```bash - if git show-ref --verify --quiet refs/heads/fix/audit-remediation; then - test "$(git rev-parse refs/heads/fix/audit-remediation)" = \ - "3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64" - test "$(git merge-base refs/heads/fix/audit-remediation origin/main)" = \ - "3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64" - fi - ``` - -4. Create or switch to the branch without force or replacement: - - ```bash - if git show-ref --verify --quiet refs/heads/fix/audit-remediation; then - test "$(git branch --show-current)" = "fix/audit-remediation" || - git switch fix/audit-remediation - else - test "$(git rev-parse HEAD)" = \ - "3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64" - git switch -c fix/audit-remediation - fi - ``` - -5. Recheck the exact post-switch object and changed-path set: - - ```bash - test "$(git branch --show-current)" = "fix/audit-remediation" - test "$(git rev-parse HEAD)" = \ - "3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64" - test "$(git merge-base HEAD origin/main)" = \ - "3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64" - test "$(git status --porcelain=v1 --untracked-files=all)" = \ - $'?? docs/implementation/audit-remediation-design.md\n?? docs/implementation/audit-remediation-plan.md' - ``` - -6. Capture exact initial file inventory and package versions. -7. Run the cheapest baseline checks: - - ```bash - git diff --check - go test ./internal/command/adoptiondoctor \ - ./internal/command/publicapi \ - ./internal/command/readinesscloseout \ - ./internal/tools/releasesbom \ - ./internal/tools/releasechange \ - ./internal/tools/coveragemetrics \ - ./internal/tools/packageverify \ - ./scripts \ - ./internal/app - ``` - -8. Do not rerun the full artifact gate before the first production tranche - unless a narrow baseline unexpectedly fails. - -### Stop condition - -Any pre-existing test failure, unknown tracked change, or head mismatch stops -implementation until it is adjudicated. It is not relabelled as an audit fix. - -## P1: Release and version-policy foundation - -### Findings - -R-10 and the versioned parts of R-02, R-09, R-24, R-26, R-28. - -### Owner changes - -- update `REQ-PROOFKIT-QUALITY-024` to own change-record schema v2, - `previousVersion`, `changeClass`, and compatible bump classification; -- update its binding and witness skeleton before adding the first red case; -- preserve registry/publication non-claims; -- add migration text that the already published `0.1.160` is immutable. - -### Test-first edits - -1. In `internal/tools/releasechange/record_test.go`, add: - - - `0.1.159 -> 0.1.160` plus breaking changes: rejected; - - `0.1.160 -> 0.2.0` plus breaking changes: admitted; - - `1.2.3 -> 1.2.4` plus breaking changes: rejected; - - `1.2.3 -> 1.3.0` plus compatible changes: admitted; - - `previousVersion >= version`: rejected; - - declared `compatible` plus required migration: rejected; - - invalid or non-canonical SemVer: rejected; - - duplicate/unknown v2 keys: rejected. - - generated npm install and rollback commands both include `--save-exact`, - and rollback names the literal `previousVersion`. - -2. Update release closeout tests to require the v2 path and version relation. -3. Confirm the new cases fail against the current v1 implementation. - -### Production edits - -1. Replace `release/change-record.v1.json` with - `release/change-record.v2.json`. -2. In `internal/tools/releasechange/record.go`: - - - set `RecordPath` to v2; - - admit `previousVersion` and closed `changeClass`; - - implement local SemVer parsing/comparison; - - derive the required change class from breaking changes and migration; - - reject patch-range-compatible breaking versions; - - render exact npm install and rollback commands from the admitted current - and previous versions. - -3. Update every exact consumer: - - - `internal/tools/releasemanifest`; - - `internal/tools/releasecloseoutinput`; - - `internal/tools/releasepreflight`; - - workflow source oracles; - - package checks; - - release documentation; - - binding and witness records. - -4. Advance synchronized version surfaces to `0.2.0`. The only authored version - owners are `package.json` and `package-lock.json`; Python metadata, artifact - names, release fixtures, and manifests are regenerated or validated - projections: - - - `package.json` and lockfile; - - Python project/package metadata; - - change record current version and `previousVersion=0.1.160`; - - release fixtures, manifests, expected artifact names, and notes. - -5. Record all intentional breaking changes already approved by the design, - including both `adoption-doctor` blocked-prerequisite state/exit and - non-enforced advisory rule-status changes, the absolute-symlink rejection, - and versioned context/browser envelopes. -6. Bind a direct current-record falsifier proving that the adoption-doctor - change and migration step reach rendered release notes. - -### Narrow gate - -```bash -go test ./internal/tools/releasechange \ - ./internal/tools/releasemanifest \ - ./internal/tools/releasecloseoutinput \ - ./internal/tools/releasepreflight -``` - -### Rollback condition - -If a live owner proves `0.2.0` conflicts with an unpublished version allocation, -stop. Do not create a special-case validator for `0.1.160`. - -## P1A: Artifact-specific SBOM dependency semantics - -### Finding - -R-02. - -### Owner-first changes - -Before the red case, update `REQ-PROOFKIT-QUALITY-002`, its binding, and its -witness skeleton to distinguish runtime dependencies of the shipped npm -artifact from source/tool module inventory. Preserve the non-claim that a -source module edge implies an installed-artifact runtime edge. - -### Test-first edits - -1. Add `TestArtifactSpecificRuntimeEdgesAndExcludedInventory` in - `internal/tools/releasesbom/main_test.go`. -2. Use two independent fixtures: - - - source/build inventory from a module graph containing source-only, - test-only, and tool modules; - - content-bound build-info inventories for individual release binaries. - -3. Require: - - - one canonical component per admitted package identity; - - every source-graph-only component has `scope=excluded`, - `proofkit:evidence-class=source_build_inventory`, and no runtime edge; - - a runtime edge exists only from the exact binary BOM reference whose - build information names the module; - - stripped binaries with no admitted build information have an empty - runtime edge set; - - distribution representations do not invent runtime edges. - -4. Mutate one source-only module into a runtime edge and prove failure. -5. Bind each binary inventory to its file content/digest so evidence from one - binary cannot authorize another binary's edge. - -### Production edits - -1. Retain `go list -m all` only as excluded source/build inventory. -2. Read each release binary with `debug/buildinfo.ReadFile` or an equivalent - content-bound parser and create `binary BOM-ref -> module` edges only from - that binary's admitted build information. -3. Deduplicate components by canonical package identity before emitting - relationships. -4. Keep package and wheel components edge-free without their own - artifact-derived evidence. -5. Preserve deterministic order and existing package/provenance non-claims. - -### Gate - -```bash -go test ./internal/tools/releasesbom -``` - -## P2: Evidence-state and CLI channel classification - -### Findings - -R-01 and R-07. - -### P2.1 Adoption blocked state - -Files: - -- `internal/command/adoptiondoctor/adoptiondoctor.go`; -- `internal/command/adoptiondoctor/adoptiondoctor_test.go`; -- `internal/app/cli_abi_test.go`; -- retirement requirement and binding records. - -Steps: - -1. Update `REQ-PROOFKIT-RETIRE-008`, its binding, non-claim, and witness - skeleton for unconditional external-precondition blocking. -2. Add `TestBuildBlocksEveryModeForExternalPreconditions`. -3. Cover `observe`, `warn`, `enforce-touched`, and `enforce-all`. -4. Add the adjacent positive case: observe-mode candidate/advisory gap remains - `passed/0` with a `skipped` rule. -5. Confirm the blocked observe/warn cases fail on current code. -6. Split unconditional blocked gaps from policy-enforced advisory gaps. -7. Compute top-level state, rule state, exit code, and promotion readiness from - the correct sets. - -Gate: - -```bash -go test ./internal/command/adoptiondoctor ./internal/kernel/adoptionmode -``` - -### P2.2 Structural input versus semantic report - -Files: - -- the nine command packages named in the design; -- `internal/app/app.go`; -- planning and agent-envelope command adapters; -- `internal/app/cli_abi_test.go`; -- CLI contract process metadata. - -Steps: - -1. Update `REQ-PROOFKIT-QUALITY-004`, the CLI process contract, and the nine - affected binding/witness - skeletons for structural-channel versus semantic-report classification. -2. Add `TestRequiredInputCommandsRouteStructuralErrorsByMode` with exact - command inventory: - - - `branch-authority`; - - `changed-path-set`; - - `deployment-evidence-admission`; - - `external-consumer`; - - `package-runtime-dependency-admission`; - - `readiness-closeout`; - - `registry-consumer`; - - `registry-consumer-proof-input-compose`; - - `repo-profile-admission`. - -3. For ordinary `{}` input assert exit 1, empty stdout, non-empty sanitized - stderr. -4. For supported explicit agent-envelope forms assert exit 1, one invalid-input - JSON envelope, empty stderr. -5. For one structurally admitted semantic failure assert nonzero exit, one - report JSON value, empty stderr. -6. Confirm all nine ordinary cases fail on current code. -7. Change builders to return admission errors rather than synthetic reports. -8. Keep envelope conversion only in the app adapter. -9. Remove any app-layer inspection of synthetic report IDs. - -Gate: - -```bash -go test ./internal/app -run RequiredInputCommandsRoute -go test ./internal/command/branchauthority \ - ./internal/command/changedpathset \ - ./internal/command/deploymentevidenceadmission \ - ./internal/command/externalconsumer \ - ./internal/command/packageruntimedependency \ - ./internal/command/readinesscloseout \ - ./internal/command/registryconsumer \ - ./internal/command/registryconsumerinputcompose \ - ./internal/command/repoprofileadmission -``` - -### Compatibility assertion - -No valid admitted semantic decision changes. Only malformed-input channel -routing and impossible `blocked -> passed` outcomes change. - -## P3: Handle-confined filesystem boundaries - -### Findings - -R-03 and R-04. - -### P3.1 TypeScript scanner - -Files: - -- `internal/command/publicapi/public_api.go`; -- `internal/command/publicapi/public_api_test.go`; -- package boundary requirement, CLI contract, and binding. - -Test-first steps: - -1. Update the package-boundary requirement, binding, non-claim, and witness - skeleton for handle-confined admission and the absolute-symlink migration. -2. Add a private staged operation seam with channels controlled by same-package - tests. -3. Add `TestVerifyRejectsDeterministicSymlinkSwap` with two mandatory table - rows: - - - leaf/source symlink swap; - - ancestor-directory symlink swap. - - For both rows: - - - pause at the exact legacy validation/use boundary; - - replace the selected component with an external sentinel route; - - release execution; - - assert error/nonzero, no successful report, and no sentinel bytes in - output. - -4. Add stable relative in-root symlink success. -5. Add absolute in-root symlink rejection with exact migration diagnostic. -6. Add canonical `.tsx` target rejection. -7. Add pinned-file size/identity change rejection. -8. Add `TestCanonicalSourceSnapshotRejectsChangedCrossAliasAdmission`: prove - that stable aliases of one canonical source reuse the first parsed snapshot, - then replace that canonical source and require a fresh alias to fail on - identity or digest drift. -9. Add `TestVerifyPinsPackageRootAcrossInRootSiblingSwap`: replace the - manifest-owning package path with an in-root sibling after source - resolution, and prove all source bytes still come from the package sub-root - pinned before manifest admission. -10. Confirm the staged redirects reproduce current vulnerability without a - polling loop. - -Production steps: - -1. Open the caller-selected repo with `os.OpenRoot`. -2. Open each referenced package as a confined pinned sub-root before reading - its manifest. -3. Admit package manifests and all named sources through that same package - sub-root. -4. Bind their open handles with `os.SameFile`. -5. Read from the pinned lexical handle under existing file and aggregate - budgets. -6. Verify pre/post identity and size. -7. Cache immutable bytes by admitted relative identity and bind every lexical - alias of one canonical source to the first admitted identity, digest, and - parsed exports. -8. Reject a later alias when canonical identity or digest differs. -9. Reject absolute symlink targets as the recorded v0.2 breaking hardening. - -Gate: - -```bash -go test ./internal/command/publicapi -go test ./internal/app -run TypeScriptPublicAPI -``` - -### P3.2 Repository-relative output writer - -Files: - -- `internal/app/requirement_commands.go`; -- `internal/app/cli_abi_test.go`; -- spec-tree output requirement and binding. - -Test-first steps: - -1. Update the spec-tree output requirement, binding, non-claim, and witness - skeleton for root-confined publication. -2. Add private root-operation injection plus exact pre-temporary-file, - pre-object-admission, and irreversible-rename parent-swap barriers. -3. Add `TestOutputWriterRejectsDeterministicParentSwap`. -4. Assert external and in-root replacement sentinels remain unchanged and no - output or temporary residue reaches the displaced parent. -5. Replace the temporary route, change its permission bits, independently add - setuid, setgid, and sticky bits, rewrite its content, and substitute a - symlink before final object admission; require exact rejection, destination - non-mutation, and cleanup in every case. -6. Preserve stable output bytes, mode `0644`, empty stdout, and cleanup. -7. Confirm the current writer is caught by the staged counterexamples. - -Production steps: - -1. Open the repository root once. -2. Create/check parents through `os.Root` and pin the admitted destination - parent. -3. Create an unpredictable temporary file through the pinned parent with - `O_CREATE|O_EXCL`. -4. Write and chmod through the file handle and retain its admitted identity, - exact mode, and expected content digest. -5. Close, cross the exact object-admission barrier, re-admit the non-symlink - temporary entry's identity, exact mode, and content digest, cross the exact - rename barrier, then re-admit the current parent route at the irreversible - rename boundary. -6. Rename to the destination and clean failures through the same pinned parent, - preserving writable-child and nested-filesystem behavior. -7. Keep static symlink/directory diagnostics without relying on them for - confinement. - -Gate: - -```bash -go test ./internal/app -run OutputWriter -``` - -### Non-claims - -No checkout freshness, compiler provenance, protection from adversarial -concurrent content or namespace mutation by the same operating-system user -during the operation, fsync durability, or repository-wide transaction is -claimed. - -## P4: Complete machine CLI contracts - -### Finding - -R-09, plus the machine projection needed by R-15 and R-24. - -### Files - -- `proofkit/cli-contract.v2.json`; -- new `internal/tools/commandcontractgen`; -- new generated `internal/app/command_contract_generated.go`; -- new generated - `internal/command/stackpreset/preset_ids_generated.go`; -- `internal/app/command_descriptors.go`; -- `internal/app/command_help.go`; -- `internal/app/cli_contract_test.go`; -- `internal/app/cli_abi_test.go`; -- `package.json`. - -Before the first red case, update `REQ-PROOFKIT-QUALITY-004` and -`REQ-PROOFKIT-PACKAGE-002`, their bindings/non-claims/witness skeletons, to -name the authored contract, both generated projections, selector-resolution -rule, and freshness gate. - -### P4.1 Define the authored contract model - -1. Add bounded `root_shape_only` input/output definitions with stable IDs and - canonical digests. -2. For all 74 required-input commands, author: - - - contract ID and schema version; - - one or more condition-complete root variants; - - root kind (`object`, `array`, or explicitly unconstrained `json_value`); - - exact closed allowed/required top-level fields for object roots; - - exact owner requirement and native admission witness selector. - -3. For every JSON-producing command, author: - - - every supported JSON-producing flag/mode condition; - - exact root kind and top-level fields for each object variant; - - first-class array variants where a command can return an array; - - exact `rootType=union` when bounded variants have more than one root kind; - - explicit unconstrained `json_value` only where no narrower root contract - is honestly owned, never as the aggregate for a bounded union. - -4. Add exact flag-value choices for stack presets from the same machine owner. -5. State explicitly that nested fields, scalar types, collection cardinality, - nullability, and cross-field semantics are outside this bounded contract. - -### P4.2 Generator - -1. Add `internal/tools/commandcontractgen`. -2. Parse the authored contract with strict duplicate-key rejection. -3. Reject the superseded inferred nested/delegated graph grammar. -4. Reject digest mismatches, missing required-input contracts, missing - JSON-output contracts, duplicate IDs, missing native witness selectors, - duplicate or empty variant conditions, missing default conditions for - optional mode flags, invalid root kinds, scalar-capable `json_value` used - for bounded unions, and allowed/required fields on non-object roots. -5. Resolve each native selector through a deterministic tracked-test inventory: - - - selector identifies an exact `_test.go` path and test function; - - Go AST confirms the function exists with a valid test signature; - - the witness command selects that test; - - stale path, nonexistent test, or unselectable command is rejected. - -6. Generate one deterministic Go map used only for private help/descriptor - metadata and one lower-package preset-ID table. Both outputs come from the - same authored input and one generator execution. -7. Support `--check` without rewriting. -8. Add: - - ```json - "command-contract:check": "go run ./internal/tools/commandcontractgen --check" - ``` - -9. Place `npm run command-contract:check` in `npm run check` before command - family and Go gates. - -### P4.3 Runtime and compatibility projection - -1. Remove manually authored schema summaries where generated metadata owns the - same fact. -2. Merge generated metadata with private runner/owner/test registration. -3. Render help from generated summaries and flag choices. -4. Include fully resolved input/output contract content and canonical digests - in the ABI projection. -5. Add mutation tests for: - - - root kind; - - allowed and required root fields; - - variant condition; - - schema version; - - deleted contract; - - digest mismatch. - - stale/nonexistent native witness selector. - - either generated output stale while the other is current. - -6. Classify `nativeAdmissionWitnessSelector` as source-checkout evidence, not a - package-consumer route. The shipped contract may name that evidence class, - but does not promise that `_test.go` files are installed. - -7. Add direct `app.Run` root-shape assertions for the high-risk multi-mode - commands, including every object/array and agent-envelope route identified - by the independent audit ledger. -8. Treat native source digests only as conservative change-review sentinels, - never as proof that the authored root shape equals native behavior. -9. Update the ABI golden only after every mutation is killed. - -### Gate - -```bash -npm run command-contract:check -go test ./internal/tools/commandcontractgen ./internal/app -run CLIContract -go test ./internal/app -run CLIABI -``` - -### Complexity guard - -Do not create a second JSON Schema runtime or make the generator validate -command input. It projects and closes the public compatibility declaration; -native admission remains command-owned. Do not infer nested ownership from AST -field-name similarity; leave nested/type/cardinality claims explicit -non-claims until an owner-generated typed schema can prove them. - -## P5: Workflow and release source oracles - -### Findings - -R-05, R-06, R-13, and R-28. - -### Files - -- `scripts/workflow_package_gate_oracle_test.go`; -- `scripts/workflow_oracle_support_test.go`; -- `scripts/workflow_browser_runtime_oracle_test.go`; -- `scripts/workflow_runtime_preconditions_test.go`; -- `scripts/workflow_source_oracles_test.go`; -- `scripts/workflow_security_scanner_oracles_test.go`; -- sorted exact inventory of every `.github/workflows/*.yml`; -- supply-chain quality requirements and bindings. - -Before the first red case, update the affected quality requirements, their -bindings/non-claims/witness skeletons, and add `REQ-PROOFKIT-QUALITY-025`. - -### P5.1 Exact expression guards - -1. Add negative fixtures for: - - - expected predicate plus `|| true`; - - `false && expected`; - - expected text only inside a quoted literal; - - whitespace inside a quoted literal; - - empty guard where not explicitly admitted. - -2. Replace substring predicates with exact per-job/per-step allowlists. -3. Normalize layout only outside quoted literals. -4. Preserve current owner-reviewed expressions byte-semantically. - -### P5.2 Exact CI aggregate - -1. Add `TestCIWorkflowDeclaresFailClosedRequiredAggregate`, - `TestCIRequiredAggregateRejectsNeutralizedScript`, - `TestCIRequiredAggregateRejectsExecutionOverrides`, and - `TestCIRequiredAggregateRejectsPlatformSmokeSubstitution` to the exact - requirement bindings. -2. Mutate fixtures with dead branch, `|| true`, early `exit 0`, and background - execution. -3. Mutate inherited workflow defaults and environment, job defaults and - environment, step environment, shell, and working-directory, and literal, - expression, explicitly false, and explicit YAML `null` forbidden fields. -4. Require exact job identifiers, provider-check names, hosted runners, needs, - `always()`, and safe workflow run defaults; require no workflow, job, or - step environment entries, reusable jobs, job defaults, or job-level - `continue-on-error` on any required leaf or aggregate job, one aggregate run - step, and no step-level execution override anywhere in those jobs. -5. Compare the whole aggregate shell block, the exact ordered macOS step - inventory with presence-aware `run`/`uses`/`with` keys, the exact - repository-local setup-action digest, and the whole fail-closed - platform-smoke package-script owner command. - -### P5.3 External action pins - -1. Complete `REQ-PROOFKIT-QUALITY-025` with: - - - external `uses` must be a 40-lowercase-hex commit; - - local `./` actions remain confined; - - no claim of action safety, tag equivalence, or provider execution. - -2. Add binding/witness IDs from the design. -3. Add `TestWorkflowExternalActionsUseFullCommitSHAs`. -4. Discover and sort the exact tracked workflow inventory, reject any - unadmitted extension/path, and inspect every YAML `uses`. -5. Replace one fixture ref with a tag and short SHA to prove failure through - the same common oracle. - -### P5.4 Existing release immutability - -1. Add `TestExistingReleasePathIsReadOnlyAndFailsOnDrift`. -2. Freeze the complete existing-release shell block as one canonical - owner-reviewed form. -3. Permit only exact release view/download provider calls inside it. -4. Remove missing-asset upload/backfill logic. -5. Make missing, extra, or different assets terminate nonzero before mutation. -6. Add negative fixtures for upload, edit, delete, `gh api`, `curl`, alternate - clients, shell indirection, and extra commands. -7. Keep new-release behavior unchanged. - -### Gate - -```bash -go test ./scripts -run 'WorkflowGuard|RequiredAggregate|ExternalActions|ExistingRelease' -npm run go:actionlint -``` - -### Non-claims - -No local oracle proves provider execution, branch protection, release asset -state, or external action safety. - -## P6: Independent blocking falsifiers - -### Findings - -R-11a through R-11d and R-12. - -### P6.1 Self-hosting verdict - -1. Apply split owner-first deltas before red tests: - - - R-11a and R-11d: - `REQ-PROOFKIT-PACKAGE-004`; - - R-11b: - `REQ-PROOFKIT-PACKAGE-006` and `REQ-PROOFKIT-QUALITY-023`; - - R-11c: - `REQ-PROOFKIT-PACKAGE-001`; - - R-12: - `REQ-PROOFKIT-QUALITY-010`. - - Update each exact binding/non-claim/witness skeleton without assigning a - neighboring invariant as owner. - -2. Extract a command-local pure verdict helper over `(process error, output - bytes)` in `scripts/validate-self-hosting-receipts.go`. -3. Keep the fixed executable invocation unchanged. -4. Add `TestRunProofkitVerdictCases` for nonzero process exit, invalid JSON, - wrong state, and - exact passed output. -5. Keep production output unchanged. - -### P6.2 Package and wheel integrity - -1. Add one-negative-at-a-time tests for: - - - npm name/version, duplicate, missing artifact; - - wheel version, duplicate, missing file, and SHA mismatch; - - every root tarball forbidden exact name and suffix; - - local versus CI receipt identity. - -2. Exercise `verifyRootPackage`, not only the leaf deny helper. -3. Use a complete minimal tarball plus exactly one forbidden entry. - -### P6.3 Coverage closure - -1. Add one case for each of the five command-route arrays. -2. Add one case for each of the four linkage dead-zone arrays. -3. Add all-empty success. -4. Require each mutation to fail at least its named negative test while the - positive fixture remains green. -5. Preserve `COVERAGE-01` as unresolved; static route metadata still does not - become semantic execution evidence. - -`npm run self:receipt` is intentionally not part of this narrow gate. It is -artifact-dependent and runs only after P10 rebuilds the current package -evidence. The executable P6 gate is: - -```bash -go test ./scripts -run 'RunProofkitVerdict|PackageArtifactRefs|PythonArtifactRefs|ReceiptID' -go test ./internal/tools/packageverify -run 'ForbiddenRootEntry|VerifyRootPackage' -go test ./internal/tools/coveragemetrics -run 'EachCommandRoute|EachLinkage' -npm run self:coverage -``` - -## P7: Honest context/diff/browser wire vocabulary - -### Finding - -R-24. - -### Files - -- `internal/command/requirementcontext`; -- `internal/command/requirementdiff`; -- `internal/command/requirementgraph`; -- `internal/command/requirementbrowser`; -- affected app ABI tests and CLI contracts; -- spec requirements and bindings. - -Before the first red case, update the context/diff/browser/graph requirements, -bindings, non-claims, and migration witness skeletons for the closed v1 adapter -and v2-only producer boundary. - -### P7.1 Versioned model - -1. Define context snapshot v2: - - ```text - expectedDigestCoverage = none | partial | all - ``` - -2. Fully admit v1 before adapting: - - ```text - unverified -> none - partially_verified -> partial - verified -> all - ``` - -3. Reject mixed v1/v2 keys. -4. Make all producers emit v2 only. - -### P7.2 Downstream envelopes - -Version and migrate separately: - -- semantic-diff input/output v2; -- workspace manifest v2; -- affected workspace API projections v2; -- CLI output contracts and golden corpus. - -Legacy terms may appear only in: - -- strict v1 adapters; -- migration fixtures; -- compatibility diagnostics. - -They must not appear in v2 output, current help, current contract descriptions, -or UI. - -### Tests - -1. `TestV1DigestCoverageAdapters`. -2. `TestV2DigestCoverageProjections`. -3. Mismatch expected/current remains rejected. -4. V1 normalized output equals the equivalent v2 semantic record. -5. Mixed fields and malformed legacy records fail. -6. Add `TestLegacyDigestVocabularyConfinedToV1AdaptersAndFixtures`. It scans - Go sources, CLI contract/help, browser assets, and test producers; legacy - vocabulary is allowed only in exact v1 adapters, migration fixtures, and - compatibility diagnostics. - -### Gate - -```bash -go test ./internal/command/requirementcontext \ - ./internal/command/requirementdiff \ - ./internal/command/requirementgraph \ - ./internal/command/requirementbrowser \ - ./internal/app -``` - -### Non-claims - -Coverage does not authenticate a baseline, producer, checkout, or freshness. - -## P8: Installed-artifact onboarding and documentation closure - -### Findings - -R-14 through R-17, R-22, R-25 through R-27, and the documentation portion of -R-10. - -Before the first red case, update the exact package/spec requirements, -bindings, non-claims, and witness skeletons for R-14 through R-17, R-22, and -R-25 through R-27. - -### P8.1 Canonical install route - -1. Update npm install to `--save-exact`. -2. Use `npm exec --offline -- agentic-proofkit` for canonical local commands. -3. Remove unverified Bun execution examples; retain a non-claim until an - equivalent exact-tarball smoke exists. -4. Explain when a bare command is valid without making it canonical. - -### P8.2 Stack preset and family discovery - -1. Add defensive-copy `stackpreset.IDs()`. -2. Project IDs from the machine CLI contract/generated metadata. -3. Direct help and invalid-ID diagnostics list all and only valid IDs. -4. Root help adds only the copyable - `npm exec --offline -- agentic-proofkit help families` discovery route. -5. Preserve token-efficient opt-in family expansion. -6. Preserve one authored vocabulary: - CLI contract -> generated lower-package table -> defensive-copy - `stackpreset.IDs()`. A sibling generated app table serves descriptors/help. - Remove any manual `presetIDs` list and prove exact bidirectional parity with - the profile map. Both generated files share one freshness gate. - -### P8.3 First valid input - -1. Add one marker-bounded minimal requirement-source JSON block to README. -2. Add the exact offline invocation. -3. State that example IDs and meaning are caller-replaceable and - non-authoritative. -4. Do not make `KnownKeys` globally verbose. - -### P8.4 Installed-artifact end-to-end witness - -Extend `internal/tools/packageverify/main_test.go` with -`TestExactTarballOnboardingTrace`: - -1. build/install the exact local tarball in a temporary consumer; -2. run root help through `npm exec --offline` and prove it exposes exactly one - copyable canonical family-discovery route; -3. parse and execute that exact displayed route, reject a bare executable - mutant, and prove family output exposes - `stack-preset`; -4. execute installed `help stack-preset` and extract every preset ID from that - human/agent help transition; -5. compare help-derived IDs bidirectionally with the installed machine - contract; the contract is a parity oracle, not a substitute UX route; -6. run every discovered stack preset ID; -7. read the marker-bounded command and JSON together from the installed - README; -8. execute exactly the admitted README argv through the same local - `npm exec --offline` and feed it the extracted JSON; -9. require exit 0, passed JSON, and empty stderr. - -### P8.5 Markdown and package reference closure - -1. Replace raw preset pipes in the contract-map table with separate code spans. -2. Add `TestContractMapDecisionTreeHasThreeCells`. -3. Remove `AGENTS.md` and `CONTRIBUTING.md` from the npm package. -4. Remove the active-backlog route from package-public README. -5. Keep `BACKLOG.md` source-checkout-only. -6. Update or exclude package projections that cite contributor-only files. -7. Treat self-hosting selectors as source-checkout evidence, not - package-consumer navigation. -8. Add field-aware `TestPackagePublicReferenceClosure`. -9. Bind mutable-release-fact policy separately to - `TestVerifyNoStalePackageDocsRejectsMutableReleaseFactsInMarkdown`; retain - reference closure only in its own PACKAGE-001 scenario. - -The closure inventory includes README Markdown destinations under the bounded -destination grammar, relative paths in the exact command-navigation statement -and owner-table cells, and every classified reference-bearing string field in -package-public machine projections. It does not claim a complete Markdown -parser or discovery of unclassified code-span paths. A relative reference is -admitted only if the normalized target is a shipped tarball entry; -source-checkout owners are denied explicitly. -`nativeAdmissionWitnessSelector` is an explicit source-checkout evidence class -and is not treated as package-consumer navigation. At minimum, kill: - -- the original `Active work ledger | BACKLOG.md` table-cell form; -- one ordinary dangling README link; -- `docs/MISSING.md` substituted into the exact README command-navigation code - span; -- one dangling package-public machine-field route; -- one false classification of a source-only witness as a shipped route. - -### P8.6 Python/platform projection - -Add a marker-bounded README block and compare it to owners: - -- macOS 12+, arm64/x64; -- Linux manylinux 2.17, arm64/x64; -- Python `>=3.9`; -- Windows unsupported; -- wrapper over the same Go CLI, not an SDK; -- conditional exact version install; -- no current PyPI availability claim. - -Document complete conditional chains: - -```text -python -m pip install agentic-proofkit== -python -m agentic_proofkit help - -uv add --dev agentic-proofkit== -uv run agentic-proofkit help -``` - -Add `TestREADMEPlatformAndPythonProjection`. -Add `TestReleaseTargetsProjectExactPythonWheelMetadata` and bind it with the -README projection plus `TestVerifyWheelContentsRequiresExactWheelMetadata` to -the exact release-platform Python-wheel scenario. - -### P8.7 Browser route diagnostic and launcher contract - -1. Include `workspace` in the command-local invalid-view diagnostic. -2. Add runtime/app parity test. -3. Refine `SPEC-021` to prohibit caller-supplied and native-witness execution - while permitting the fixed OS launcher. -4. Inject launcher operation in tests. -5. Require fixed executable/argv forms and server-generated loopback URL. -6. Add `TestOpenBrowserUsesFixedLauncherAndLoopbackURL`. -7. Keep that selector in the fixed-launcher scenario; bind the distinct - one-shot cleanup scenario to the three exact `TestServeOneShot*` cleanup and - concurrency tests. - -### Gate - -```bash -go test ./internal/command/stackpreset ./internal/app -go test ./internal/tools/packageverify -run 'OnboardingTrace|PackagePublicReferenceClosure' -go test ./internal/tools/pythonpackage ./internal/kernel/releaseplatform -go test ./internal/command/requirementbrowser -run 'InvalidView|OpenBrowser|ServeOneShot' -``` - -## P9: Browser state, accessibility, reflow, and contrast - -### Findings - -R-18 through R-23. - -Before the first red case, update the browser requirements, bindings, -non-claims, and witness skeletons for the state matrix, semantics, reflow, -target-size, and contrast contracts. - -### P9.1 Production state model - -1. Server HTML includes a visible bootstrap-loading state. -2. Initialization removes the capability token before API use. -3. Manifest fetch is inside a bounded initializer with sanitized terminal - failure. -4. View failures and handoff failures use distinct stable state IDs. -5. Active view controls expose `aria-current`. - -### P9.2 Native semantics - -1. Replace synthetic tree/treeitem roles with `ul`, `li`, and `article`. -2. Remove roving tab index and ArrowUp/ArrowDown handler. -3. Add a visible `Handoff packet` heading. -4. Make the packet a semantic region with `aria-labelledby` pointing to that - visible heading. -5. Preserve Tab/Shift+Tab, Enter/Space, selection, Unicode coordinates, and - handoff semantics. -6. Assert `getByRole("region", { name: "Handoff packet" })` in empty, - successful, and failed handoff states. - -### P9.3 Layout and colors - -1. Add `box-sizing`, `min-width:0`, and `max-width:100%` to grid regions. -2. Allow long human text and IDs to wrap. -3. Wrap navigation. -4. Add labelled internal graph and table scroll viewports. -5. Never hide document overflow globally. -6. Define explicit light/dark foreground, background, border, and focus tokens. -7. Preserve forced-colors adaptation. - -### P9.4 Deterministic state matrix - -Refactor `tests/browser/workspace.spec.mjs` around a table: - -```text -state -setup/barrier -expected data-state and heading -default axe -explicit target-size -320px reflow -contrast when applicable -``` - -Required rows: - -- bootstrap loading with deferred manifest; -- bootstrap failed; -- specifications loading with a deferred requirements response; -- specifications; -- specifications no-match with an admitted empty requirement projection; -- diff loading with a deferred diff response; -- diff; -- graph loading with a deferred graph response; -- graph; -- unavailable diff/graph; -- view request failed; -- handoff result; -- handoff failed. - -For every row: - -1. Assert body and stable content-substate identity before any oracle. -2. Run default axe and require no violations. -3. Explicitly enable `target-size`, require applicability, and require no - violations. -4. At 320 by 800, assert no document-level horizontal overflow. -5. Permit only labelled graph/table internal overflow. -6. Compute effective composited colors from actual rendered and focused - controls and their adjacent backgrounds in light and dark schemes for - Chromium, Firefox, and WebKit. -7. Require text contrast at least `4.5:1` and boundary/focus contrast at least - `3:1`. - -In a separate negative test, render an intentionally undersized control, -explicitly enable `target-size`, require applicability, and require at least -one violation. The production state matrix contains only zero-violation rows. - -### Gate - -```bash -npm run browser:static-check -npm run browser:test -``` - -### Non-claims - -No full WCAG 2.2 conformance, branded Safari, complete screen-reader -interoperability, every OS theme, or 400-percent zoom claim is added. - -## P10: Durable proof parity and current artifacts - -### Objective - -Validate that implementation evidence, not the temporary design/plan, owns -every repair. Exact owners and binding skeletons were already created before -their P1-P9 counterexamples. - -### Requirement-source parity - -Confirm only these exact affected invariants changed: - -- `REQ-PROOFKIT-RETIRE-008`; -- `REQ-PROOFKIT-PACKAGE-001`, `002`, `003`, `004`, `005`, `006`, `007`; -- `REQ-PROOFKIT-SPEC-001`, `009`, `011`, `018`, `019`, `021`; -- `REQ-PROOFKIT-SPEC-022`, `023`; -- `REQ-PROOFKIT-QUALITY-002`, `004`, `005`, `006`, `007`, `010`, `011`, - `013`, `016`, `019`, `022`, `023`, `024`; -- add `REQ-PROOFKIT-QUALITY-025`. - -Do not broaden unrelated requirements. - -### Implementation correction epoch - -Apply the design's C-01 through C-124 corrections before final parity: - -1. prove one immutable release byte snapshot owns hash and build information - before a same-handle content/identity recheck, and bind TypeScript canonical - first-admission bytes/digest/identity/parsed exports with deterministic - same-lexical and cross-alias swap falsifiers; -2. reject any CLI command whose honest root-shape input/output projection is - absent, open, structurally invalid, condition-ambiguous, or missing a - supported variant; cover explicit and omitted defaults with direct public - CLI oracles, while retaining nested fields, leaf types, collection - cardinalities, and nullability as explicit non-claims; -3. reject witness selectors that are named declarations but not valid - functions in active `_test.go` files discovered by the current Go build; -4. admit breaking major and pre-1.0 minor version increases without requiring - a `.0` target, while preserving breaking-patch rejection; -5. treat `SPEC-022` and `SPEC-023` as direct schema-v2 consumer owners; -6. keep source-hygiene identifier-sensitive without treating coincidental - content-digest substrings as organization-policy leakage; -7. derive receipt-kind mismatch fixtures from the current binding complement - so a legitimate proof-route expansion cannot silently invert the test; -8. pin package and output parent sub-roots, bind each admitted route to its - handle, and reject both outside-root and in-root sibling substitutions; -9. render blocked and enforceable adoption gaps as disjoint classes and keep - unresolved external prerequisites blocking in every mode; -10. admit plural native source ownership only as a sorted, non-empty, unique - alternative to singular ownership, with freshness checks for every source; -11. route nested deployment scanner admission failures through structural - `stderr`, not synthetic semantic JSON; -12. declare and execute both submitted and terminal one-shot browser output - variants; -13. admit the pilot `all` mode through one strict two-input envelope and prove - the package and public CLI routes; -14. include unstaged tracked deletions in package-artifact snapshot identity; -15. preserve the valid root package in forbidden-root-entry mutation fixtures; -16. remove only report helpers proven unreachable after structural-channel - migration, then refresh their native-source review sentinels; -17. require the CI aggregate to omit job-level `continue-on-error` rather than - attempting partial GitHub-expression truth evaluation; -18. hold specifications, diff, and graph requests independently and run the - complete browser state oracle against each visible loading state. -19. model workflow, job, and step execution controls in the typed workflow - oracle; reject unsafe inherited defaults, unexpected environment entries, - job defaults, shell or working-directory overrides, and any - `continue-on-error` presence on all required leaf and aggregate jobs or any - of their steps. -20. declare the intentional `adoption-doctor` blocked-state and nonzero-exit - breaking change plus its consumer migration step in the versioned change - record, and prove that both reach rendered notes. -21. declare the intentional non-enforced advisory rule transition from - `passed` to `skipped`, prove its migration reaches rendered notes, require - exact `observe=skipped` and `warn=warning` rule statuses, prove skipped - rules outside an `enforce-touched` selection preserve top-level `passed/0`, - and bind both adjacent semantic oracles to `REQ-PROOFKIT-RETIRE-008`. -22. retain an attempt-scoped browser report and test-results directory after a - failed proof command, upload only those diagnostic paths under exact - `failure()` semantics, and keep the passed proof artifact on its existing - success-only path; bind both lifecycle and workflow selectors only to the - `REQ-PROOFKIT-QUALITY-022` browser artifact-confinement and - failure-diagnostics-retention witnesses. -23. initialize the exact pinned axe distribution through the browser-context - script channel behind a removable anti-corruption module and require its - reachable builder entrypoint to evaluate only a deterministically tested - constant loader; configure retained traces to preserve actions, DOM - snapshots, network, and sources while disabling continuous screenshots; - request only one bounded best-effort screenshot after a failure, preserve - the default and target-size axe oracles plus the undersized-control negative - fixture, and reject either isolated control, retries, or a timeout increase - as unsupported alternatives; run repeated clean first-attempt browser - proofs before final exact-object closeout. -24. keep output temporary-file creation and cleanup on the pinned admitted - parent, but publish only through the repository root with full source and - destination routes; interleave an exact `before_publish` parent move and - prove that outside-root and in-root replacement sentinels, destination - bytes, and displaced temporary-file residue all remain unchanged or absent. -25. supersede C-28's substituted-source route by staging the temporary object - at the repository root, retaining its identity, and re-admitting that - identity plus the destination-parent route after the exact barrier; keep - publication and cleanup repository-root-confined, refresh dependent native - source projections, reject a deterministic temporary-route substitution - before final admission, and explicitly exclude adversarial same-user - namespace mutation after final admission because the cross-platform - standard-library surface cannot atomically prove both current-root ancestry - and pinned-parent identity. -26. supersede root-level C-29 staging by returning temporary creation, cleanup, - and publication to the pinned destination parent; after the exact barrier - re-admit the non-symlink temporary entry identity, exact mode, and content - digest, then re-admit the current parent route at the irreversible rename - boundary; falsify object replacement, permission drift, each special mode - bit, in-place rewrite, symlink aliasing, and parent replacement - independently; and retain same-user - concurrent content/namespace mutation as an explicit non-claim. -27. add the compatible repository-confined same-parent atomic output guarantee - to the machine release record and prove its exact summary reaches rendered - release notes. -28. construct a canonical closeout record from the exact final tree, diff - counts, admitted local gate facts, residual non-claims, and retrospective; - bind it by digest and unique sentinels in the reviewed body snapshot and - independently in the final server body. -29. copy every local evidence artifact used by the closeout projection once - into a private snapshot, validate and project only those exact bytes, and - recheck final `HEAD` plus tracked-tree cleanliness after record - construction; compare complete output-file mode to exactly `0644` and - independently falsify setuid, setgid, and sticky mutations. -30. remove every local artifact field not admitted by an exact closeout - predicate; retain only final-SHA provenance, enumerated state/count facts, - and explicitly checked command, tree-state, status, and exit values. -31. slurp every local snapshot during validation, require exactly one JSON - document, apply its full predicate to that object, and admit the exact - Chromium, Firefox, and WebKit project inventory before projection. -32. replace the workflow literal-disabled deny-list with closed required-CI and - release job inventories plus presence-aware exact absent-or-owner - conditions for every job and step; preserve only named conditional - exceptions and falsify dynamic false and explicit-null conditions on - required CI test steps and release candidate routes. -33. bind every required CI job to its exact provider-check name and runner, - require the macOS job's exact fail-closed platform-smoke command, reject - no-op and reusable-job substitutions, require positive owner admission - before each bound mutation table, and route the positive CI inventory - selector through both owning quality requirements. -34. replace normalized runner lists with exact scalar comparisons, close the - complete ordered macOS step inventory and exact package-script owner, - bind one positive selector to both real CI and release package-gate - workflows, falsify semantic shadowing and mixed-type runners, and execute - the restored singleton coverage filter before closeout. -35. admit the exact repository-local setup-action bytes, falsify a nested - action semantic shadow, make `run`, `uses`, and `with` key presence part of - exact step comparison, and reject whitespace or explicit-empty dual - execution syntax. -36. require the exact complete selector sets for the QUALITY-011 aggregate and - QUALITY-013 package-gate anti-vacuity scenarios, falsify missing and surplus - selectors, parse the marker-bounded README argv with a boundary-local - expansion-free literal shell-word grammar, preserve safe single quotes, - double quotes, escapes, and adjacent literal segments, and reject - operators, expansion, globbing, multiline input, and malformed quoting. -37. key every protected selector inventory by its exact requirement/scenario - pair and falsify an owner-only transfer of each newly protected critical - scenario. -38. compare required selector sets before the generic empty-selector path, - falsify complete deletion for both critical scenarios, reject NUL in every - lexer state, and require no unresolved confirmed finding from every final - reviewer. -39. trim only Bash space/tab command delimiters, preserve vertical-tab and - non-breaking-space argv bytes plus exact JSON fence bytes, reject escaped - NUL and unescaped double-quoted history expansion, and preserve the literal - Bash semantics of double-quoted `\\!`. -40. trim only leading Bash space/tab delimiters, leave trailing delimiters to - the lexer, and preserve trailing escaped space and tab in the final argv. -41. route the generic missing-selector-function mutant through an unprotected - binding so it remains independent from exact-set admission. -42. consume complete backslash runs before history markers and preserve exact - Bash-equivalent argv for quoted and unquoted run lengths one through four. -43. remove every severity cutoff from candidate-preparation evidence - disposition. -44. complete the pure exact-inventory phase before generic AST and active-file - validation so negative inventory mutants fail without repeated I/O. -45. consume every backslash run once for linear command parsing and retain a - 128-KiB non-history regression. -46. compose pure inventory and generic executability validation in production - while routing partial fixtures only through their owning phase. -47. bind generic missing-function and invalid-signature falsifiers to - QUALITY-010 and protect its complete four-selector inventory against empty, - missing, surplus, and owner-transfer mutants. -48. classify the shared workflow-oracle tests by owner, bind all seventeen - QUALITY-013 typed package-gate selectors, and protect the complete set - without absorbing tests owned by other requirements. -49. declare the existing readiness-closeout one-pass strict - character-reference policy as a breaking release change with exact - migration and rendered-note projection, admit zero or one Go-test - parameter name, bind the unnamed-parameter regression, expand the exact - QUALITY-010 executability inventory to five selectors, and protect the - three-selector QUALITY-024 release-record inventory. -50. split original Markdown structural segments before one-pass - character-reference decoding and falsify an encoded pipe; add the stable - specifications no-match substate to every browser oracle; disclose the - removed synthetic Arrow-key contract with migration guidance and the - compatible pilot-all envelope plus optional witness-selector I/O in the - machine release record and exact rendered-note witness; refresh the - readiness native-source, generated CLI-contract, and public ABI golden - projections. -51. project the copyable offline npm exec family-discovery route in root help, - parse and execute that displayed command in the exact-tarball consumer, - reject a bare-route mutant, and project the compatible correction through - release notes plus the app native-source, generated CLI-contract, and - public ABI golden projections. -52. remove only authored leading ASCII space/tab indentation before parsing - the displayed npm route, preserve its trailing bytes, and reject both - leading and trailing NBSP mutants. -53. display, parse, and execute every installed onboarding transition from - family discovery through leaf help, every contract-owned preset, the - displayed installed README path, and the README first-valid-input command; - reject bare or missing intermediate routes. -54. protect the exact onboarding release addition, classify removal of - installed governance files as breaking with migration guidance, and cover - TypeScript manifest ancestors plus sources in the absolute-symlink - migration. -55. admit exact ordered step inventories for CI source quality, CI browser - runtime, and the release candidate, including execution-key presence and - values; reject inserted npm-script shadow steps. -56. record every tracked path crossing the deterministic size threshold, - separate browser/runtime/source/scanner/support workflow responsibilities, - retain the logically inseparable QUALITY-011/013 cluster, update moved - binding paths, and protect PACKAGE-005, QUALITY-022, and QUALITY-025 - selector inventories against empty, missing, surplus, owner-transfer, and - stale-path mutants. -57. add `id` and `timeout-minutes` values and presence to each exact step - inventory, and reject both fields in source-quality, browser-runtime, and - release-candidate jobs. -58. require every protected selector inventory to retain its exact - `witnessPath`, and reject relocation independently. -59. freeze the exact five-file untracked inventory before staging, stage the - reviewed tracked-plus-untracked path union, and require no unstaged or - untracked remainder. -60. recompute the final threshold ledger and correct every stale LOC or byte - measurement, including `internal/app/app_test.go`. -61. bind the extracted CodeQL, OSV, and Scorecard scanner selectors to - QUALITY-005, QUALITY-006, and QUALITY-007 with exact selector and path - inventories. -62. traverse every family and leaf route from the exact installed tarball, - execute each displayed route, and require every installed invocation to be - the exact npm prefix plus its bare usage before following preset and README - continuations. -63. retain candidate path inventories in memory and feed the closed untracked - inventory to `git add` through stdin, preserving exact staged equality - without temporary-file cleanup. -64. require exact workflow, advisory, and provider permission maps for CodeQL, - OSV, advisory Scorecard, and public Scorecard, including explicit - inheritance; reject a missing floor, advisory write, and surplus provider - write independently. -65. collect unique Usage and Installed invocation lines before comparison, - require their authored order and exact command-token boundary, and reject - installed-before-Usage plus prefix-collision mutants. -66. bind the C-69 falsifier to QUALITY-019, protect the exact six-selector set - and witness path, and align the owner requirement with every displayed - family and leaf transition. -67. require the final browser closeout snapshot to contain exactly 31 executed - and passed tests for each of Chromium, Firefox, and WebKit, matching the - current 93-test committed gate. -68. declare `cli_flag_conjunction_v1` only for a root-shape definition whose - conditions close one identical sorted allowed-flag dimension set; reject - malformed, non-canonical, missing, surplus, type-mixed, or overlapping - assignments; enumerate the native-owner mode and pilot domains over all 80 - combinations and require exactly twelve declared valid states; derive each - ABI condition from parsed argv; reject repeated `--mode` and `--pilot`; - update the public ABI digest, requirements, bindings, release record, - migration, and rendered notes. -69. extend the decomposition-owner inventory from the five extracted workflow - owners to those files plus - `internal/tools/commandcontractgen/condition_model.go`; retain exact staged - path equality and empty unstaged and untracked remainders. -70. reject an explicitly empty `--pilot` value before option normalization, - require its exact CLI diagnostic without any root-condition projection, - and include the breaking rejection in the owning requirements, public ABI, - release record, migration, and rendered notes. -71. distinguish the current correction worktree from the baseline-relative - candidate: admit exactly one current untracked condition-model owner, stage - the exact correction path union, and independently prove that the resulting - baseline-relative additions contain all six decomposition-owner files. -72. derive the test's mode and pilot domains from immutable copies of the same - internal native lists that construct option-admission maps, while retaining - exact 80-combination and twelve-valid-state assertions. -73. restrict the condition-model opt-in to the current adoption output - definition, reject an unowned second definition, and require any future - extension to arrive with its own native-domain and argv-closure proof. -74. close the complete eighteen-file baseline-relative addition inventory after - staging and prove that the six decomposition owners are a subset rather - than misreporting that subset as the whole candidate delta. -75. bind the reachable guidance mode/scope JSON failure argv to its canonical - guidance condition and `06-guidance-report` root variant before inspecting - the body. -76. require the condition model to be referenced only by the exact adoption - command, output direction, and adoption output definition; reject aliases - of each component independently. -77. remove volatile exact line counts from decomposition rationale and retain - snapshot measurements only in the deterministic threshold ledger. -78. require every JSON assertion case to carry both exact route coordinates, - reject coordinates on non-JSON cases, and run condition and variant - assertions unconditionally for every JSON-emitting argv. -79. require actual non-empty JSON stdout exactly when a case declares a JSON - assertion, parse every observed stdout, and preserve the exact sorted - fourteen-case JSON inventory against whole-case deletion. -80. replace the condition-model file's duplicate generic sorted-key helper with - the existing same-package `sortedKeys` owner and remove the unused import. -81. replace the Mach-O compatibility scenario's README-only selector with the - exact negative, boundary-positive, truncated-parser, and legacy-parser - tests; add the requirement/scenario pair and witness path to the coverage - owner's exact critical inventory, and include it in the existing empty, - missing, surplus, owner-transfer, and relocation mutation table. -82. require exact selector/path inventories for release-platform Python-wheel - parity and browser one-shot cleanup; add one all-target wheel - metadata/filename projection test, retain README and verifier witnesses, - replace the cleanup route's fixed-launcher selector with the exact three - cleanup/concurrency tests, and keep the launcher in its separate existing - package-boundary scenario. -83. replace the mutable-release-fact scenario's reference-closure selector with - the existing ten-class stale-package-doc falsifier, retain reference - closure in PACKAGE-001, and add the PACKAGE-007 requirement/scenario/path - tuple to the five-class exact inventory mutation oracle. -84. require each CodeQL, OSV, and Scorecard workflow job inventory to equal its - expected advisory/provider union before permission validation, and reject - an otherwise valid unclassified job carrying `contents: write`. -85. add one immutable `cliexec` renderer with exact `npm_offline`, - `python_module`, and `path` profiles; make npm and Python wrappers overwrite - and export `AGENTIC_PROOFKIT_LAUNCHER_PROFILE` and - `AGENTIC_PROOFKIT_PYTHON_EXECUTABLE`; admit exactly absent-or-`path` with no - executable, `npm_offline` with no executable, or `python_module` with one - absolute executable containing no report-visible secret-like or Unicode - control content and reject every other combination without disclosing the - value; admit once at the Go process boundary without ambient autodetection; - thread it explicitly through help, stack-preset, - gradual-adoption-bootstrap including the adoption aggregate route, - project-structure, agent-route, adoption-workflow, and - requirement-coverage producers; test the exact display and structured-argv - paths listed by D-09, including caller-owned array prefixes and - native-witness argv, renderer-owned suffixes, envelope command refs, - decoded materialization payloads, and project workflow source-report - identity; in - the exact tarball consumer require every emitted preset command to carry - the npm-offline prefix and execute one exact self-continuation; in - `internal/tools/pythonpackage/continuation_test.go` build and install the - current native wheel in a temporary venv, require the absolute - venv-interpreter module prefix, execute one exact self-continuation, - traverse root/family/leaf help, and directly execute exact emitted - agent-route argv with npm absent from `PATH`; reject malformed profiles, - secret/control paths, wrong-profile, bare, missing, surplus, - field-relocation, caller-rewrite, Unicode-whitespace, and shell-expansion - mutants; bind the - exact D-09 requirement/scenario/path/selector/command rows and protect them - in the coverage critical inventory; update - `REQ-PROOFKIT-PACKAGE-002`, `REQ-PROOFKIT-PACKAGE-003`, - `REQ-PROOFKIT-PACKAGE-006`, `REQ-PROOFKIT-QUALITY-019`, their exact - bindings, and the breaking change plus migration witnessed by - `REQ-PROOFKIT-QUALITY-024` / - `TestCurrentChangeRecordNamesReviewedSemanticChanges`. -86. close the root-distinct native-output inventory over exactly - `adoption-contract-envelope`, `pilot-admission`, and `self-check`; set their - selectors to `internal/app/cli_abi_test.go` / - `TestAdoptionContractEnvelopeCLIABI`, - `TestStandaloneMultiVariantCommandsUseExactRootShapes`, and the new focused - `TestSelfCheckOutputUsesExactRootShape` with each exact anchored - `go test ./internal/app -run` command; add `internal/app` to pilot output - `nativeSources`, retain exact adoption - `{internal/command/adoptioncontract}` and self-check `{internal/app}` source - sets, and recompute all affected canonical digests and projections; add an - exact `command + direction + native-source-path-set + path + test + - executable command + requirement + scenario` inventory tying CLI contract - selectors and native owners to requirement bindings; map all three - selectors to `proofkit.package-boundary.cli-output-root-witnesses` and - `proofkit.supply-chain-quality.cli-abi-golden`, plus adoption to - `proofkit.spec-proof-core.adoption-contract-envelope-cli-abi`; reject empty, - missing, surplus, selector-substitution, source-set-substitution, - nativeSource/nativeSources downgrade, path-relocation, direction-transfer, - scenario-transfer, command-drift, and owner-transfer mutants; update - `REQ-PROOFKIT-PACKAGE-002`, `REQ-PROOFKIT-QUALITY-004`, - `REQ-PROOFKIT-SPEC-011`, their exact binding scenarios/selectors, and both - generated CLI-contract projections; own the tuple-closure oracle through - `proofkit.supply-chain-quality.cli-output-witness-contract` at - `internal/app/cli_output_witness_contract_test.go`, selector - `TestRootDistinctOutputWitnessBindingsAreExact`, rather than enlarging the - general CLI topology corpus. C-90 does not change public output roots or - require a consumer migration. -87. harden `python_module` launcher admission so the executable is a non-empty - absolute path without report-visible secret-like, Unicode control, or - Unicode format content; reject with field-only errors that do not disclose - the value and exercise the shared redaction fixture corpus plus complete - bidi-control mutants. Update - `REQ-PROOFKIT-PACKAGE-002`, its overview projection, and D-09's exact - admission matrix. C-91 adds no credential model or new secret scanner. -88. extend the generated-route closure across the root family route, every - family and leaf help route, every descriptor installed invocation, help - forms, stack-preset copyable routes, agent-route next-command and envelope - argv, direct and aggregate adoption-workflow phase/envelope argv, - requirement-coverage rerun argv, and project workflow plan argv; require - display fields to equal `cliexec.DisplayArgv(argv)` where present. Build the - project workflow plan and its source-report stable hash with the same - renderer, while separately proving that caller-owned native-witness argv is - byte-preserved. Bind - `TestGeneratedCommandInvocationProfileRouteClosure` beside the string-field - inventory selector and update the coverage exact set. Expand the installed - Python wheel trace through root help, every family and leaf help route, - agent-route emission, and direct execution of exact emitted argv with npm - absent from `PATH`; admit exact four-space route indentation and canonical - command operands, reject Unicode-whitespace and shell-expansion mutants, - and never execute generated stdout through a shell. Update - `REQ-PROOFKIT-PACKAGE-002`, `REQ-PROOFKIT-PACKAGE-006`, their overview and - bindings, D-09, and the breaking summary plus migration under - `REQ-PROOFKIT-QUALITY-024`. C-92 adds no public JSON field, ambient - resolver, or general shell parser. -89. make the output-writer identity mutant cross-platform deterministic: - create one ordinary replacement file with the exact expected bytes and mode - before writer entry, prove it coexists with and has an identity distinct - from the writer-created temporary object, then at `before_publish` remove - the temporary entry and rename the live replacement into that pathname. - Require the exact identity diagnostic, unchanged destination bytes, and no - `.proofkit-output-*` residue; repeat the complete parent-swap test enough - times to falsify accidental inode-allocation dependence. Neutralizing both - identity checks must make this same-content, same-mode mutant fail red. - C-93 changes no production behavior or public contract. -90. replace only the graph table's two page-realm `evaluateAll` calls with one - shared web-first assertion helper. For each node and edge sequence require - exact row count and `data-identity` equality at every index. Preserve the - complete interaction flow, all later graph assertions, one worker, zero - retries, and the 30-second timeout; falsify missing, surplus, reordered, - duplicated, substituted, and absent identities. C-94 changes no browser - product behavior or evidence count. -91. before typed workflow decoding, traverse raw YAML nodes for the exact seven - tracked workflow owners. Require closed workflow, job, and step key sets; - reject duplicate and merge keys; admit job `environment` only for release - `publish` as exact `npm-production` and release `publish-pypi` as the exact - `{name,url}` owner mapping. Add owner-positive coverage plus root, producer, - aggregate, step, reusable-job, local-action-escape, environment, and unknown - mutants; bind the selector under `REQ-PROOFKIT-QUALITY-025`. C-96 changes no - workflow bytes or provider claim. -92. add `.css` to the existing source-hygiene text inventory. Derive one - synthetic filename for every unique extension returned by `git ls-files` - for the tracked requirement-browser asset owner, and run the existing - staged-blob and current-worktree mutants over that closed set plus Markdown - and Python. Keep the digest-substring test and token matcher unchanged. - C-97 adds no MIME, encoding, or binary-detection abstraction. -93. replace the two selection scenarios' repeated `getAttribute`, - `waitForFunction`, `page.evaluate`, query scan, clamping, and manual event - dispatch. Use Playwright `selectText` and click for ordinary selection and - collapse. For the exact emoji selection, read the locator text in the test - runner, compute strict UTF-16 and code-point bounds independently, then use - one locator-scoped operation and require the exact quote, nonzero start, a - two-code-unit DOM span, and one-code-point output span. Do not add retries, - browser branches, production hooks, timeout, workers, or new test - identities. C-98 changes no product behavior. -94. distinguish `audit_baseline_sha=3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64` - from - `integration_base_sha=0df4c28bac9737f476f7dc66030363b8b40d5417` - throughout P12.4-P12.6. Bind final parent, remote `main`, PR base, diff, - provider projection, and closeout integration identity to the latter; - retain the former only as named historical evidence. Admit the exact - observed first-publication feature lease - `1a681c47911680d101d36b48ce818ea1905a7148`, then bind every later - correction publication to its exact current remote head and fail before - mutation when any identity differs. C-99 changes no product behavior or - provider state. -95. require the final coverage snapshot to contain exactly 69 requirements, - 69 bound requirements, 173 scenarios, and 78 commands. Exercise stale 167 - and adjacent 172/174 counts against the singleton snapshot predicate. - C-100 changes only closeout evidence admission. -96. reject Unicode categories `Cc` and `Cf` at Python launcher admission, - retain the existing non-disclosing field-only diagnostic, and exhaustively - equate the helper classification plus admission/non-disclosure result with - every Unicode scalar in `Cc or Cf`, including non-bidi `Cf`. Update - `REQ-PROOFKIT-PACKAGE-002`, its overview projection, the breaking change - record, its migration, and the exact release-projection oracle. C-101 adds - no Unicode normalization or general path-policy layer. -97. update `REQ-PROOFKIT-PACKAGE-002`, `REQ-PROOFKIT-QUALITY-004`, their - overview projections, and the existing PACKAGE-002/QUALITY-004 - `TestStandaloneMultiVariantCommandsUseExactRootShapes` binding. Preserve - `--stack-diverse` as a compatible `pilot-admission` alias and add - `--contract-envelope --stack-diverse` to the exact input/output condition - inventories and public root-shape witness. Reject repeated `--pilot`, - repeated `--stack-diverse`, and both mixed orders with one stable - selector-ambiguity diagnostic before input admission. Record the formerly - accepted repeated-selector routes as breaking and add an exact migration - step. Regenerate both CLI contract projections and update exact condition - inventories plus the release-projection oracle. C-102 changes no valid - single-selector route result or output shape. -98. replace the manually selected current change-record assertions with exact - ordered equality for all breaking change IDs/summaries, addition - IDs/summaries, and migration steps. Require byte-for-byte equality with one - independently authored complete current release-note projection, including - every section and boundary. Delete each current entry; substitute every - change ID, summary, and migration step; swap every adjacent pair; add one - valid surplus to each machine inventory; reorder or relocate a note bullet; - and add an in-block, appended, duplicate, or second-section note. Require the - existing - `TestCurrentChangeRecordNamesReviewedSemanticChanges` selector to reject - every mutant. Update `REQ-PROOFKIT-QUALITY-024` and its overview while - retaining the existing binding and selector. C-103 adds no source-diff - inference, public record field, or runtime behavior. -99. replace the publication refspec's unbraced variable-colon form with - `"${final_sha}:refs/heads/fix/audit-remediation"`. Require a complete scan - of this tracked plan to contain no unbraced shell variable immediately - followed by a colon before publication, then execute the corrected refspec - under the existing exact remote lease. C-104 adds no product behavior or - permanent shell framework. -100. after the first successful correction publication, preserve - `1a681c47911680d101d36b48ce818ea1905a7148` only as its historical lease - and require the next publication lease to equal current remote head - `90090a5c712efa70b900fed0e115274cfa4773f0`. Abort before mutation on any - mismatch and require the remote to equal the new literal final SHA after - publication. C-105 adds no product or provider-state claim. -101. after every later owner-file addition and document correction, recompute - the complete baseline-relative added-path inventory. Require the final set - to contain exactly 21 paths, including - `internal/app/cli_output_witness_contract_test.go`, - `internal/app/invocation_profile_test.go`, and - `internal/tools/pythonpackage/continuation_test.go`. At the C-106 freeze, - require exactly the two reviewed design/plan paths and an empty untracked - set before staging; retain the six-file decomposition-owner subset. - C-106 adds no source or runtime behavior. -102. replace the Scorecard public-publish input predicate with exact equality - over the three required output inputs and their values. Add a `repo_token` - surplus mutant that must fail through - `TestScorecardPublicPublishDeclaresRequiredOutputInputs`. C-107 changes no - workflow byte or public behavior. -103. recompute the base-to-candidate `requirementId -> invariant` delta and - require the P10 list to contain all 30 changed IDs with no surplus, - including `SPEC-011` and `QUALITY-005`, `QUALITY-006`, and `QUALITY-007`. - C-108 changes only temporary parity evidence. -104. remove every numeric line-count qualifier from the four - reverse-decomposition bullets instead of refreshing the stale values. - C-109 preserves every owner and split/merge disposition. -105. require `publish_results` to decode as the exact YAML boolean `true`, - remove the generic truth-expression helper from this predicate, and add a - `true }}` string-substitution mutant beside the C-107 surplus mutant. - C-110 changes no workflow byte. -106. time-index every historical 21-path and two-file staging statement to the - C-106 freeze and the later three-file correction statement to the C-114 - freeze; retain P12.2 as the sole current correction-path owner. C-111 - changes no staged path by itself. -107. require the selected named public-publish step to be the sole - `ossf/scorecard-action` step. Add a differently named second action with a - surplus `repo_token` input and require the same boundary predicate to - reject it. C-112 adds no full step inventory or workflow change. -108. split each action reference at one `@`, compare only owner/repository to - `ossf/scorecard-action` with ASCII case-insensitive equality, preserve the - ref bytes, and reject mixed-case second-action, distinct-repository, - subpath, empty-ref, and repeated-`@` mutants. C-113 adds no general action - parser or network dependency. -109. reject every non-ASCII repository byte before case-insensitive - comparison, retain ASCII mixed-case admission, and reject a Unicode - simple-fold long-s mutant. C-114 adds no normalization or provider lookup. -110. replace the `@axe-core/playwright` two-audit topology with one direct - pinned `axe-core` audit per state. Test first: - - freeze exact run options to the sole - `rules.target-size.enabled = true` override with no `runOnly`; - - register exact `{content: axeDistributionSource}` exactly once per test - context and reject zero, wrong, or surplus registrations; - - use a dedicated Playwright fixture whose fresh `Page`/`BrowserContext` - owns one test state, initializes before the body, and admits teardown - only after exactly one audit; reject sequential or concurrent duplicate - registration/audit attempts before their second page-realm operation, - prove failed-registration rollback, and make multi-state reuse of one - page lifetime a non-claim; - - require the exact pre-run version before any `configure` or `run`, exact - wrapper-equivalent same-origin and `playwright` branding configuration, - and returned `testEngine` pair `("axe-core", "4.12.1")`; - - require `page.frames()` to equal exactly `[page.mainFrame()]`; - - reject zero or child frames before evaluation and require zero evaluation - calls for both frame mutants; reject absent/wrong versions or callables, - missing/wrong/surplus configure and run options, a two-rule `runOnly`, - a default-rule override, wrong/missing result identity, more than one - evaluation, and any temporary-page access; - - in one runtime negative fixture use exactly one undersized named control - attributed to `target-size` and one independently normal-sized unnamed - control attributed to default `button-name`; require both violations, - then preserve the production zero-violation, target applicability, and - target incomplete predicates; - - remove `@axe-core/playwright` from package and lock inventories and from - the package verifier's exact source-only toolchain owner and fixture; - add a dedicated exact dependency-inventory test whose missing, wrong, - and surplus subcases otherwise satisfy every earlier manifest predicate - and therefore reach the `devDependencies` map-equality rejection; use - retained `@axe-core/playwright` as the surplus mutant; - - update only QUALITY-022's exact requirement sentence and overview - projection; retain existing binding and witness identities; - - preserve one worker, zero retries, 30 seconds, and the diagnostic policy. - Regenerate the exact dependency lock and run the narrow owners first: - - ```bash - npm uninstall --save-dev @axe-core/playwright - node --test \ - --test-name-pattern='^browser accessibility harness closes direct audit topology$' \ - scripts/browser-proof-inputs.test.mjs - go test ./internal/tools/packageverify \ - -run '^TestVerifyRootManifestBoundaryRejectsDevDependencyDrift$' - npm run browser:static-check - ``` - - The C-115 epoch was executed and falsified at iteration 15 as recorded in - review history. Do not rerun or relabel its first 14 passes as success. - Apply step 111 before running the replacement C-116 epoch below. -111. replace the three avoidable graph pass-path operations with one bounded - response-derived, web-first witness in the existing - `workspace renders admitted views and creates a keyboard-authorized - handoff` test. Test first and keep the total test identity count at 25: - - retain the exact failed C-115 trace/report identities above and make no - product or exact Firefox/Juggler cause claim; - - before Traceability activation, install one exact same-origin POST route - and request/response observers for `/api/v1/graph`; fetch the real - upstream response, require a non-empty node projection and a deterministic - sentinel absent from the upstream bytes, replace only the first node - label, fulfill the app-issued request, and require exactly one request, - interception, linked successful response, and rendered sentinel; - - implement that route and both observers as one attempt-scoped state - machine `armed -> intercepted -> admitted -> detached`. - `route.fetch({timeout: 5000, maxRedirects: 0, maxRetries: 0})` must retain - the original same-origin URL and return success. Fetch, parse, or fulfill - failure must terminally abort an unfinished route. A `finally` path must - await every started callback, remove the exact route and both listeners - on success, assertion failure, and timeout, and fail the witness if - detachment is incomplete. Close the proof window only after the sentinel, - exact counters, linked response, and successful detachment are admitted; - - deep-copy and recursively freeze the admitted response projection. - Derive expected node/edge identities, exact node/edge table rows, full - and Unicode-code-point-truncated labels, viewBox, node positions, and - non-degenerate edge endpoints only from that frozen response and the - independently restated layout formula; - - add one immutable exact assertion plan and local closed-record/list - helpers in `tests/browser/workspace.spec.mjs`. Execute every - `(surface, element, assertion, expected)` entry through one branch-free - injectable executor. The production sink performs and awaits the - Playwright matcher; a recording sink must receive and complete the exact - ordered call inventory. Missing, surplus, skipped, duplicated, or - unresolved-call mutants must fail. The same assertion plan must drive - synchronous one-dimension mutation tables; do not add a second helper - file or duplicate the contract in a source-string scanner; - - equate the response with exact graph datasets, root child order, line and - group identities, `title, rect, text` order, childlessness, attributes, - direct text, computed rectangle geometry, tables, captions, headers, - rows, and cells. Require zero document elements from the exact - declarative SVG animation set `animate`, `animateColor`, `animateMotion`, - `animateTransform`, `discard`, and `set`; - - use retryable locator assertions for the exact owned ancestor chain, - viewport, SVG, nodes, edges, and both table projections. Close the - design's exact display, visibility, opacity, transform family, overflow, - zoom, content visibility, animation, transition, filter, clip, mask, - paint, geometry, font, text-layout, text-security, row/cell visibility, - count, and ordering inventories. Do not use `boundingBox()`, raw - pass-path computed-style evaluation, element screenshots, or - `toBeVisible()` on SVG lines; - - make every named C-116 falsifier executable through the shared closed - inventories. Pure topology/style mutation tables must change one owned - dimension while retaining all unrelated dimensions; cross-engine facts - already established during design adjudication need not add repeated - page-realm mutation operations. The runtime sentinel remains the causal - cache falsifier; - - require independent mutants for response omission/cache, graph/table - order/count/text, hidden or alpha-zero surfaces, every retained style - key, geometry/transform/text layout, inert leaf/root topology, positive - transition duration, zero-duration positive delay, phase-split local and - ancestor CSS animation, external graph and ancestor SMIL source - admission, zero-dash and degenerate edges, static caption/header - substitution, and visible trust-cell glyph loss; - - preserve exact console-error absence, all existing trust-state content, - handoff behavior, one worker, zero retries, 30-second tests, - failure-only screenshot policy, and screenshot-free traces. Do not - change production CSS, workspace rendering, requirement text, binding, - route, public API, packet, or business logic for C-116; - - run the narrow static owner first: - - ```bash - npm run browser:static-check - ``` - - Then freeze every browser-runtime input and let iteration 1 of the - complete executable C-116 epoch below be the first replacement-path - Firefox runtime. Do not add a public single-project selector merely for - this correction. Any timeout or test mismatch blocks closeout and - requires trace adjudication. Playwright 1.61.1-versus-1.62 or - browser-lifetime A/B remains nonblocking after a 30-of-30 pass and - becomes blocking only after another replacement-path timeout or an exact - engine/lifetime cause claim. -111a. because the first provider execution after the 1.61.1 epoch stalled in a - different raw Playwright page operation, execute the isolated 1.62 A/B: - - change only `package.json`, `package-lock.json`, - `internal/tools/packageverify/main.go`, and - `internal/tools/packageverify/main_test.go`; - - require all four owners to admit exact version `1.62.0`; - - preserve every browser test, one worker, zero retries, the 30-second - timeout, shipped CLI JSON/exit-code behavior, product runtime, and - business logic; admit the nonbreaking source-only public package-metadata - pin and the lock-resolved Node `>=20` development-toolchain engine floor - without projecting that floor onto consuming runtime requirements; - - reject rerun-only evidence, retry or timeout inflation, test deletion, - and simultaneous browser-lifetime or raw-operation changes because each - either weakens the falsifier or mixes causal variables; - - run the package-verifier suite, then treat the version change as a new - resolved input digest and execute the complete 30-process epoch below - from iteration 1, followed in the same frozen-input sequence by both full - browser proofs, the composite gate, and the full gate; - - after committed-object review, require fresh provider attempt 1. A green - attempt admits the candidate without proving version-only causation. Any - local verdict, identity, package-admission, security, or platform - regression rolls back all four owner changes together. Another isolated - provider stall rejects a version-only conclusion and routes to a - separately reviewed browser-lifetime or remaining raw-operation - experiment with a new digest and complete epoch; - - create no new file, adapter, retry layer, configuration surface, or - version registry: the four existing owners are the minimum synchronized - boundary and there is no second consumer for another abstraction. -111b. correct C-117 in `internal/app/cli_abi_test.go` only: - - before creating the setgid temp object, normalize the test root group to - the caller's effective group so Darwin can materialize the special bit; - - after every mode `Chmod`, require immediate exact `Lstat` mode equality; - - retain separate permission, setuid, setgid, and sticky-bit mutants and - leave the production writer unchanged; - - prove the setgid case 100 of 100 times with the default temp root and 100 - of 100 times with the retained foreign-group `TMPDIR`, then run the full - gate; - - verify that the resolved browser input digest remains the Playwright 1.62 - epoch digest. Any digest change restarts that epoch from iteration 1; - - reject skip, deletion, rerun-only acceptance, a weaker expected error, or - a production change because none proves that the intended mutant exists; - - add no platform adapter, test file, or production hook without a second - consumer. If a supported Darwin or Linux filesystem cannot materialize - the postcondition after group normalization, stop for a separately - reviewed platform-specific fixture. -111c. correct C-118 in the existing workspace asset and browser witness: - - first add a delayed-handoff counterexample that submits from - Specifications, opens Diff, then releases the handoff response; - - require the packet region to publish the valid result while the global - state and active-view control remain Diff; - - capture the current view request identity at submission and condition - only the handoff success/failure global state write on identity equality; - - do not abort, retry, discard, or resubmit the handoff and do not change - server one-shot semantics. -111d. correct C-119 in the existing browser witness: - - retain both provider-attempt report and trace identity sets and classify - the four Firefox failures only from their observed evidence; - - route all then-current workspace opens and reloads - through owner-local helpers using `waitUntil: "commit"`; - - require a non-null successful main-resource response and the visible - server-owned workspace heading through Playwright's exact accessible-name - matcher for every helper call, and retain every downstream semantic - assertion; - - leave the `about:blank` axe negative-control navigation unchanged; - - add no lifecycle-event wait, retry, timeout increase, production change, - `networkidle` heuristic, or swallowed navigation failure. -111e. correct C-120 in the same browser witness: - - intercept the main document and preserve its original body while - returning status 503 for open and reload in independent cases; - - require each helper to reject with its owner-specific terminal error; - - preserve successful navigation while changing only the heading to - `browser.fixture.workspace drift`, so a substring matcher survives but - the exact matcher rejects; - - admit the original status 200, byte-changing substitution, and completed - route fulfillment independently so a fixture or route-handler failure - cannot satisfy the expected rejection; - - execute all three falsifiers in Chromium, Firefox, and WebKit and add no - production hook, retry, timeout increase, or new file. -111f. correct C-121 in the same browser witness and its existing static owner: - - retain exact provider run `30337477288`, browser job `90205431977`, - artifact `8679825289`, report and trace digests, and synthetic merge - identity as the counterexample to `waitUntil: "commit"`; - - admit the raw configured base URL as exact local HTTP root authority, - capture the current main frame, and arm an exact-URL main-frame - navigation-response waiter before scheduling either location assignment - or reload; - - require one exact trigger token, a successful response, and the exact - visible server-owned heading; abort and consume the pending waiter after - any trigger or admission failure; - - add a pure classifier truth table for main-frame navigation, same-URL - main-frame fetch, same-URL child-frame navigation, and foreign URL; - - rehearse independent deletion of the exact-URL, navigation, main-frame, - response-status, and exact-heading clauses and require the corresponding - Chromium falsifier to fail; - - deliver a same-URL 503 fetch before a classifier-only reload and prove - that only the navigation response is admitted; - - close the source corpus to 27 open-helper calls, two reload-helper calls, - one classifier-only reload trigger, and no direct workspace lifecycle - wait; preserve only the isolated `about:blank` axe control; - - execute all 31 tests in Chromium, Firefox, and WebKit; retain zero - retries and the existing timeout; add no production change, readiness - hook, dependency churn, or new file. -111g. correct C-122 in P12.2: - - make its declarative current correction inventory and executable expected - inventory name the same three exact post-`c2315fd` paths; - - retain the independent empty-untracked, six-owner-subset, 21-addition, - staged-equality, and empty-remainder predicates; - - reject a historical qualifier because both inventories own the same - current staging operation. -111h. correct C-123 in the existing navigation-classifier test: - - admit the configured base URL positively before navigation; - - reject non-string input, HTTPS, hostname drift, missing port, username, - password, non-root path, query, and fragment as independent rows; - - keep username and password counterexamples distinct; - - rehearse deletion of at least one high-boundary local-origin clause and - require its row to fail; - - retain 31 tests per engine by extending the cohesive C-121 test rather - than adding a second browser lifecycle. -111i. correct C-124 in the same test: - - replace assertion-library token matching with one exact terminal owner - error so its failure identity is stable; - - inject a pending response waiter whose abort signal rejects it and whose - fallback resolves to a distinct unsuccessful response; - - return a wrong trigger token and require the token error, observed abort, - and observed waiter-rejection consumption; - - require exact `waiter-armed`, `trigger-called`, `waiter-aborted`, - `waiter-consumed` order so pre-arm sequencing is executable; - - independently delete token admission, signal abort, and explicit - consumption in Chromium and require each owning assertion to fail; - - retain the real-page decoy path, one runtime test identity, zero retries, - and no production or general mocking surface. -112. after C-93, C-94, C-96 through C-124, and all document edits freeze, - recompute the - complete threshold ledger and correct every stale path, LOC, or byte count - before committed-object review. C-95 adds no decomposition requirement. - -### C-116 executable falsifier epoch - -The Firefox stress evidence is diagnostic bounded-reliability evidence, not a -new merge-proof artifact. It uses one immutable materialized browser-input -snapshot, one bounded server, 30 separate Playwright processes, and 30 unique -attempt directories. Every iteration must prove exactly the same 25 test -identities with 25 expected passes, zero skipped, unexpected, or flaky tests, -one worker, zero retries, and 30-second tests. The first failure stops the epoch -and retains its report, trace, screenshots, source snapshot, and server logs. -The historical identity authority is the exact sorted title projection from -the hash-verified C-115 iteration-15 report: -`sha256:f7b80cd6ea950cad6693a7b11020f746581d6eba4f2b7314700e4161448a554c`. -It is the SHA-256 of the no-newline compact JSON bytes produced by the same -`file::suite > title` jq projection below. Iteration 1 and every stress and -full Firefox projection must equal this digest; iteration 1 is not allowed to -redefine identity authority. -An outer watchdog gives every process a finite wall-clock deadline, owns its -detached process group, forwards wrapper interruption, and rejects a -successful leader that leaves descendants. It returns an ordinary child -result only after proving process-group absence. If bounded -initial forwarded or cleanup signal followed by `KILL` cannot prove absence, -it returns the recorded non-zero `kill-deadline-exceeded` blocker; deadline -and descendant cleanup use `TERM`, while wrapper interruption first forwards -its actual signal. Any signal or group-probe error is retained in the final -record and conservatively treated as continuing residual-process risk. A -blocking result admits no stress or full-gate evidence. - -Run from the repository root: - -```bash -set -euo pipefail -stress_root="$(mktemp -d "${TMPDIR:-/tmp}/proofkit-firefox-stress.XXXXXX")" -export PROOFKIT_FIREFOX_STRESS_ROOT="$stress_root" -PROOFKIT_BROWSER_INPUT_RESOLUTION="$( - go run ./internal/tools/browserproofverify --resolve-inputs -)" -export PROOFKIT_BROWSER_INPUT_RESOLUTION -frozen_input_digest="$( - node --input-type=module <<'NODE' -import {createHash} from "node:crypto"; -import {symlinkSync} from "node:fs"; -import {join, resolve} from "node:path"; -import { - loadBrowserProofInputResolution, - materializeInputSnapshot, -} from "./scripts/browser-proof-inputs.mjs"; - -const root = process.env.PROOFKIT_FIREFOX_STRESS_ROOT; -const resolution = loadBrowserProofInputResolution(); -const source = join(root, "source"); -const assets = materializeInputSnapshot(resolution.inputPaths, ".", source); -symlinkSync(resolve("node_modules"), join(source, "node_modules"), "dir"); -const inputResolution = { - serverTarget: resolution.serverTarget, - writerPath: resolution.writerPath, -}; -const value = createHash("sha256") - .update(JSON.stringify({assets, inputResolution})) - .digest("hex"); -process.stdout.write(`sha256:${value}`); -NODE -)" -test -n "$frozen_input_digest" -stress_source="$stress_root/source" -materialized_input_digest() { - node --input-type=module <<'NODE' -import {createHash} from "node:crypto"; -import {join} from "node:path"; -import { - loadBrowserProofInputResolution, - snapshotInputAssets, -} from "./scripts/browser-proof-inputs.mjs"; - -const root = process.env.PROOFKIT_FIREFOX_STRESS_ROOT; -const resolution = loadBrowserProofInputResolution(); -const assets = snapshotInputAssets( - resolution.inputPaths, - join(root, "source"), -); -const inputResolution = { - serverTarget: resolution.serverTarget, - writerPath: resolution.writerPath, -}; -const value = createHash("sha256") - .update(JSON.stringify({assets, inputResolution})) - .digest("hex"); -process.stdout.write(`sha256:${value}`); -NODE -} -test "$(materialized_input_digest)" = "$frozen_input_digest" -root_input_digest() { - node --input-type=module <<'NODE' -import {createHash} from "node:crypto"; -import { - loadBrowserProofInputResolution, - snapshotInputAssets, -} from "./scripts/browser-proof-inputs.mjs"; - -const resolution = loadBrowserProofInputResolution(); -const assets = snapshotInputAssets(resolution.inputPaths); -const inputResolution = { - serverTarget: resolution.serverTarget, - writerPath: resolution.writerPath, -}; -const value = createHash("sha256") - .update(JSON.stringify({assets, inputResolution})) - .digest("hex"); -process.stdout.write(`sha256:${value}`); -NODE -} -test "$(root_input_digest)" = "$frozen_input_digest" -run_with_deadline() { - deadline_seconds="$1" - watchdog_record="$2" - shift 2 - node --input-type=module - \ - "$deadline_seconds" "$watchdog_record" "$@" <<'NODE' -import {spawn} from "node:child_process"; -import {writeFileSync} from "node:fs"; - -const [deadlineText, recordPath, command, ...args] = process.argv.slice(2); -const deadlineSeconds = Number(deadlineText); -if ( - !Number.isSafeInteger(deadlineSeconds) || - deadlineSeconds <= 0 || - typeof command !== "string" || - command.length === 0 -) { - process.exit(125); -} -const startedAt = new Date().toISOString(); -let child; -let cleanupStatus; -let cleanupWrapperExitCode; -let deadlineTimer; -let groupPoll; -let finished = false; -let leaderExitCode = null; -let leaderSignal = null; -let pendingWrapperSignal; -const groupProbeErrors = []; -const signalErrors = []; -let termTimer; -let killTimer; -let dispatchWrapperSignal = (value) => { - pendingWrapperSignal ??= value; -}; -process.on("SIGINT", () => { - dispatchWrapperSignal({ - signal: "SIGINT", - status: "wrapper-sigint", - wrapperExitCode: 130, - }); -}); -process.on("SIGTERM", () => { - dispatchWrapperSignal({ - signal: "SIGTERM", - status: "wrapper-sigterm", - wrapperExitCode: 143, - }); -}); -child = spawn(command, args, {detached: true, stdio: "inherit"}); -const record = (status) => { - writeFileSync(recordPath, `${JSON.stringify({ - args, - command, - deadlineSeconds, - leaderExitCode, - leaderSignal, - groupProbeErrors, - signalErrors, - startedAt, - status, - })}\n`, {encoding: "utf8", mode: 0o600}); -}; -const groupExists = () => { - try { - process.kill(-child.pid, 0); - return true; - } catch (error) { - if (error?.code === "ESRCH") return false; - if (error?.code === "EPERM") return true; - groupProbeErrors.push({code: error?.code ?? "unknown"}); - return true; - } -}; -const signalGroup = (signal) => { - try { - process.kill(-child.pid, signal); - } catch (error) { - if (error?.code !== "ESRCH") { - signalErrors.push({code: error?.code ?? "unknown", signal}); - } - } -}; -const finish = (status, wrapperExitCode) => { - if (finished) return; - finished = true; - clearTimeout(deadlineTimer); - clearTimeout(termTimer); - clearTimeout(killTimer); - clearInterval(groupPoll); - record(status); - process.exit(wrapperExitCode); -}; -const beginCleanup = (status, wrapperExitCode, signal) => { - if (cleanupStatus !== undefined) return; - cleanupStatus = status; - cleanupWrapperExitCode = wrapperExitCode; - record(`${status}:${signal.toLowerCase()}-requested`); - signalGroup(signal); - groupPoll = setInterval(() => { - if (!groupExists()) finish(cleanupStatus, cleanupWrapperExitCode); - }, 100); - termTimer = setTimeout(() => { - if (!groupExists()) { - finish(cleanupStatus, cleanupWrapperExitCode); - return; - } - record(`${status}:kill-requested`); - signalGroup("SIGKILL"); - killTimer = setTimeout(() => { - if (groupExists()) { - finish("kill-deadline-exceeded", wrapperExitCode); - return; - } - finish(cleanupStatus, cleanupWrapperExitCode); - }, 5_000); - }, 5_000); -}; -child.once("error", (error) => { - record(`spawn-error:${error.code ?? "unknown"}`); - process.exit(125); -}); -child.once("exit", (code, signal) => { - leaderExitCode = code; - leaderSignal = signal; - if (cleanupStatus !== undefined) { - return; - } - if (groupExists()) { - beginCleanup("descendants-remain", 125, "SIGTERM"); - return; - } - finish("exited", Number.isInteger(code) ? code : 125); -}); -dispatchWrapperSignal = ({signal, status, wrapperExitCode}) => { - beginCleanup(status, wrapperExitCode, signal); -}; -if (pendingWrapperSignal !== undefined) { - dispatchWrapperSignal(pendingWrapperSignal); -} -deadlineTimer = setTimeout(() => { - beginCleanup("deadline-exceeded", 124, "SIGTERM"); -}, deadlineSeconds * 1_000); -NODE -} -stress_server="$stress_root/server" -( - cd "$stress_source" - run_with_deadline 300 "$stress_root/server-build-watchdog.json" \ - go build -o "$stress_server" ./internal/tools/browsertestserver -) -server_stdout="$stress_root/server.stdout" -server_stderr="$stress_root/server.stderr" -"$stress_server" >"$server_stdout" 2>"$server_stderr" & -server_pid=$! -stop_stress_server() { - stop_signal="" - if kill -0 "$server_pid" 2>/dev/null; then - kill -TERM "$server_pid" - stop_signal="TERM" - for _ in $(seq 1 100); do - if ! kill -0 "$server_pid" 2>/dev/null; then - break - fi - sleep 0.1 - done - if kill -0 "$server_pid" 2>/dev/null; then - kill -KILL "$server_pid" 2>/dev/null || true - stop_signal="KILL" - for _ in $(seq 1 50); do - if ! kill -0 "$server_pid" 2>/dev/null; then - break - fi - sleep 0.1 - done - fi - fi - if kill -0 "$server_pid" 2>/dev/null; then - printf 'stress server did not terminate after %s\\n' "$stop_signal" >&2 - return 1 - fi - set +e - wait "$server_pid" - server_status=$? - set -e - case "$server_status" in - 0|137|143) ;; - *) - printf 'stress server terminal status %s is not admitted\\n' \ - "$server_status" >&2 - return 1 - ;; - esac -} -trap stop_stress_server EXIT -for _ in $(seq 1 300); do - if grep -q '^Proofkit requirement browser: http://127\.0\.0\.1:[0-9][0-9]*/$' \ - "$server_stdout"; then - break - fi - if ! kill -0 "$server_pid" 2>/dev/null; then - sed -n '1,120p' "$server_stderr" >&2 - exit 1 - fi - sleep 0.1 -done -stress_url="$( - sed -n \ - 's/^Proofkit requirement browser: \(http:\/\/127\.0\.0\.1:[0-9][0-9]*\/\)$/\1/p' \ - "$server_stdout" | - head -n 1 -)" -test -n "$stress_url" -stress_records="$stress_root/records.jsonl" -historical_test_ids_digest="sha256:f7b80cd6ea950cad6693a7b11020f746581d6eba4f2b7314700e4161448a554c" -expected_test_ids="" -for iteration in $(seq 1 30); do - test "$(materialized_input_digest)" = "$frozen_input_digest" - iteration_id="$(printf '%02d' "$iteration")" - iteration_root="$stress_root/iteration-$iteration_id" - mkdir -p "$iteration_root" - report_path="$iteration_root/playwright-report.json" - output_path="$iteration_root/test-results" - set +e - ( - cd "$stress_source" - PROOFKIT_BROWSER_TEST_URL="$stress_url" \ - PROOFKIT_BROWSER_TEST_REPORT_PATH="$report_path" \ - PROOFKIT_BROWSER_TEST_OUTPUT_DIR="$output_path" \ - run_with_deadline 900 "$iteration_root/watchdog.json" \ - node node_modules/@playwright/test/cli.js test --project=firefox - ) - exit_status=$? - set -e - current_input_digest="$(materialized_input_digest)" - test "$current_input_digest" = "$frozen_input_digest" - report_status=1 - watchdog_status="$( - jq -r .status "$iteration_root/watchdog.json" 2>/dev/null || - printf 'invalid' - )" - test_ids="" - executed_count=0 - passed_count=0 - if test -f "$report_path"; then - executed_count="$( - jq '[.suites[].specs[].tests[]] | length' "$report_path" 2>/dev/null || - printf '0' - )" - passed_count="$( - jq '[ - .suites[].specs[].tests[].results[] | - select(.status == "passed") - ] | length' "$report_path" 2>/dev/null || - printf '0' - )" - if jq -e ' - .errors == [] and - .config.workers == 1 and - ([.config.projects[] | - select( - .name == "firefox" and - .retries == 0 and - .repeatEach == 1 and - .timeout == 30000 - )] | length) == 1 and - .stats.expected == 25 and - .stats.skipped == 0 and - .stats.unexpected == 0 and - .stats.flaky == 0 and - ([.suites[].specs[].tests[]] | length) == 25 and - all( - .suites[].specs[].tests[]; - .projectName == "firefox" and - .status == "expected" and - (.results | length) == 1 and - .results[0].status == "passed" and - .results[0].retry == 0 - ) - ' "$report_path" >/dev/null; then - report_status=0 - test_ids="$( - jq -c \ - '[.suites[] | - .file as $file | - .title as $suite | - .specs[] | - "tests/browser/\($file)::\($suite) > \(.title)" - ] | sort' \ - "$report_path" - )" - fi - fi - test_ids_digest="$( - printf '%s' "$test_ids" | - shasum -a 256 | - awk '{print "sha256:" $1}' - )" - jq -cn \ - --argjson iteration "$iteration" \ - --arg inputDigest "$current_input_digest" \ - --arg project firefox \ - --arg testIdsDigest "$test_ids_digest" \ - --arg watchdogStatus "$watchdog_status" \ - --argjson executed "$executed_count" \ - --argjson passed "$passed_count" \ - --argjson exitStatus "$exit_status" \ - --argjson reportStatus "$report_status" \ - '{ - iteration: $iteration, - project: $project, - executed: $executed, - passed: $passed, - inputDigest: $inputDigest, - testIdsDigest: $testIdsDigest, - watchdogStatus: $watchdogStatus, - exitStatus: $exitStatus, - reportStatus: $reportStatus - }' >>"$stress_records" - test "$exit_status" -eq 0 - test "$watchdog_status" = "exited" - test "$report_status" -eq 0 - test "$test_ids_digest" = "$historical_test_ids_digest" - if test "$iteration" -eq 1; then - expected_test_ids="$test_ids" - else - test "$test_ids" = "$expected_test_ids" - fi -done -test "$(materialized_input_digest)" = "$frozen_input_digest" -test "$( - jq -s ' - length == 30 and - ([.[].iteration] == [range(1; 31)]) and - ([.[].inputDigest] | unique | length) == 1 and - ([.[].testIdsDigest] | unique) == - ["sha256:f7b80cd6ea950cad6693a7b11020f746581d6eba4f2b7314700e4161448a554c"] and - all(.[]; - .project == "firefox" and - .executed == 25 and - .passed == 25 and - .exitStatus == 0 and - .reportStatus == 0 and - .watchdogStatus == "exited" - ) - ' "$stress_records" -)" = "true" -test "$(jq -r .inputDigest "$stress_records" | sort -u)" = \ - "$frozen_input_digest" -kill -0 "$server_pid" -stop_stress_server -trap - EXIT -# Do not edit a browser-proof input after stress materialization. Run two -# owner-valid complete proofs, then the composite and full gates in this shell. -test "$(root_input_digest)" = "$frozen_input_digest" -browser_proof_firefox_test_ids() { - jq -c '[ - .projects[] | - select(.name == "firefox") | - .testIds[] - ] | sort' artifacts/proofkit/browser-runtime-proof.json -} -assert_historical_firefox_test_ids() { - current_firefox_test_ids="$(browser_proof_firefox_test_ids)" - current_firefox_test_ids_digest="$( - printf '%s' "$current_firefox_test_ids" | - shasum -a 256 | - awk '{print "sha256:" $1}' - )" - test "$current_firefox_test_ids" = "$expected_test_ids" - test "$current_firefox_test_ids_digest" = "$historical_test_ids_digest" -} -run_with_deadline 1800 "$stress_root/first-full-watchdog.json" \ - npm run browser:test -first_full_digest="$(jq -r .inputDigest artifacts/proofkit/browser-runtime-proof.json)" -test "$first_full_digest" = "$frozen_input_digest" -assert_historical_firefox_test_ids -test "$(root_input_digest)" = "$frozen_input_digest" -run_with_deadline 1800 "$stress_root/second-full-watchdog.json" \ - npm run browser:test -second_full_digest="$(jq -r .inputDigest artifacts/proofkit/browser-runtime-proof.json)" -test "$second_full_digest" = "$frozen_input_digest" -test "$first_full_digest" = "$second_full_digest" -assert_historical_firefox_test_ids -test "$(root_input_digest)" = "$frozen_input_digest" -run_with_deadline 1800 "$stress_root/composite-watchdog.json" \ - npm run browser:check -composite_digest="$(jq -r .inputDigest artifacts/proofkit/browser-runtime-proof.json)" -test "$composite_digest" = "$frozen_input_digest" -assert_historical_firefox_test_ids -test "$(root_input_digest)" = "$frozen_input_digest" -run_with_deadline 3600 "$stress_root/full-check-watchdog.json" \ - npm run check -assert_historical_firefox_test_ids -test "$(root_input_digest)" = "$frozen_input_digest" -``` - -No product or proof correction is complete from an implementation change -alone. Its applicable negative test, durable requirement, binding selector, -and generated projection must agree before P11. Temporary closeout corrections -C-99, C-100, C-104 through C-106, C-108, C-109, and C-111 instead require -their exact executable identity/count predicates, named counterexamples, and -independent plan review; they do not invent durable product requirements, -bindings, or generated projections. - -### Overview projections - -Update each touched spec overview only where the requirement's summarized -claim changes. Overview prose must not become an alternate policy owner. - -### Bindings and witness plan - -1. Confirm every exact scenario/witness route from the design's durable proof - table was applied owner-first. -2. Point falsifier witnesses to test files, not production-only files, where a - test now owns the negative case. -3. Add new command-contract generator check and action-pin witness commands. -4. Preserve environment classes and network non-claims. -5. Recompute binding and witness canonical order. - -### Backlog - -- do not claim `COVERAGE-01` closed; -- keep signed-tag and live-release rows blocked; -- update only rows whose source-local obligation actually changed; -- do not add provider success. - -### Release records - -Record: - -- public process-channel correction; -- context/diff/browser schema v2 migration; -- absolute symlink hardening; -- package-doc contraction; -- SBOM semantic correction; -- help/onboarding additions; -- accessibility behavior change; -- exact previous version and breaking change class. - -### Gate - -Run the exact owner and projection tests, rebuild current artifact evidence, -and only then run receipt/coverage validation: - -```bash -npm run go:test -npm run command-contract:check -npm run command-family:check -npm run package:artifact -npm run self:receipt -npm run self:coverage -``` - -## P11: Review and full closeout - -### P11.1 Local structural review - -1. Run `gofmt` on changed Go files. -2. Run `git diff --check`. -3. Confirm no Cyrillic entered tracked files. -4. Confirm no generated artifacts, package tarballs, caches, or proof residue - are tracked except exactly these two owner-admitted projections generated - together and freshness-checked by the same - `npm run command-contract:check` invocation: - - - `internal/app/command_contract_generated.go`; - - `internal/command/stackpreset/preset_ids_generated.go`. -5. Inspect `git diff --stat` and exact changed paths. -6. Use semantic diff to identify changed functions/types and unexpected blast - radius. - -### P11.2 Preliminary multi-agent worktree review - -Use three read-only reviewers against the final worktree: - -- proof/security/confinement and false-green oracles; -- contract/release/package and proof-routing closure; -- UX/accessibility/onboarding and business-logic preservation. - -Each finding remains a hypothesis until root reproduction. Fix confirmed -findings and rerun their narrow gates. These worktree reviews reduce risk but -do not replace the committed-object review in P12. - -### P11.3 Full gate - -Run: - -```bash -git diff --check -npm run check -``` - -`npm run check` must include `command-contract:check`. - -Any skipped or unavailable gate is reported with its exact blocker and is not -success. - -### P11.4 Candidate preparation - -After full worktree green: - -1. require exact evidence for all remaining confirmed objections and findings; -2. rerun any narrow gate affected by closeout edits; -3. verify the temporary docs contain no unresolved `pending` review state; -4. set design and plan status to `implementation candidate`, not - `implemented/validated`; -5. freeze the candidate path inventory and expected diff before the identity - check and commit. - -### Residual non-claims - -Final closeout must state: - -- no npm/PyPI/GitHub live publication proof; -- no branch-protection or tag-ruleset proof; -- no retroactive correction of `0.1.160`; -- no vulnerability absence or action safety; -- no full `COVERAGE-01` closure; -- no complete fuzz-space or performance-regression proof; -- no full WCAG 2.2 conformance; -- no consumer adoption or production readiness. - -## P12: Commit, publish branch, and open pull request - -### P12.1 Identity and authority check - -Immediately before external mutation: - -```bash -gh api user --jq .login -gh api repos/research-engineering/agentic-proofkit --jq .permissions -git config user.name -git config user.email -``` - -Require: - -```text -GitHub login = iperev -permissions.push = true -git user.name = iperev -``` - -An identity mismatch stops publication. Do not switch to an account with only -pull permission. - -### P12.2 Candidate commit - -1. Freeze the reviewed correction-path inventory before staging. In the - current correction epoch, require it to be exactly: - - ```text - docs/implementation/audit-remediation-design.md - docs/implementation/audit-remediation-plan.md - tests/browser/workspace.spec.mjs - ``` - - Require the complete untracked inventory to be empty. - - Independently require this six-file decomposition-owner subset: - - ```text - internal/tools/commandcontractgen/condition_model.go - scripts/workflow_browser_runtime_oracle_test.go - scripts/workflow_oracle_support_test.go - scripts/workflow_runtime_preconditions_test.go - scripts/workflow_security_scanner_oracles_test.go - scripts/workflow_source_oracles_test.go - ``` - - Require the complete candidate index to add exactly these twenty-one files - relative to the reviewed baseline: - - ```text - docs/implementation/audit-remediation-design.md - docs/implementation/audit-remediation-plan.md - internal/app/cli_output_witness_contract_test.go - internal/app/command_contract_generated.go - internal/app/invocation_profile_test.go - internal/command/requirementbrowser/requirementbrowser_test.go - internal/command/requirementbrowser/v1_adapter.go - internal/command/requirementcontext/v1_adapter.go - internal/command/requirementdiff/v1_adapter.go - internal/command/stackpreset/preset_ids_generated.go - internal/tools/commandcontractgen/condition_model.go - internal/tools/commandcontractgen/main.go - internal/tools/commandcontractgen/main_test.go - internal/tools/pythonpackage/continuation_test.go - release/change-record.v2.json - scripts/workflow_browser_runtime_oracle_test.go - scripts/workflow_oracle_support_test.go - scripts/workflow_runtime_preconditions_test.go - scripts/workflow_security_scanner_oracles_test.go - scripts/workflow_source_oracles_test.go - tests/browser/axe-harness.mjs - ``` - - Then stage exactly the current three-file correction inventory, prove - staged-path equality, prove the six-file owner subset, and prove the - complete twenty-one-file baseline-relative addition set: - - ```bash - set -euo pipefail - baseline_sha="3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64" - correction_path_inventory="$( - { - git diff --name-only HEAD - git ls-files --others --exclude-standard - } | LC_ALL=C sort -u - )" - expected_correction_path_inventory="$( - printf '%s\n' \ - docs/implementation/audit-remediation-design.md \ - docs/implementation/audit-remediation-plan.md \ - tests/browser/workspace.spec.mjs | - LC_ALL=C sort - )" - expected_untracked_inventory="" - expected_decomposition_owner_inventory="$( - printf '%s\n' \ - internal/tools/commandcontractgen/condition_model.go \ - scripts/workflow_browser_runtime_oracle_test.go \ - scripts/workflow_oracle_support_test.go \ - scripts/workflow_runtime_preconditions_test.go \ - scripts/workflow_security_scanner_oracles_test.go \ - scripts/workflow_source_oracles_test.go | - LC_ALL=C sort - )" - expected_baseline_added_inventory="$( - printf '%s\n' \ - docs/implementation/audit-remediation-design.md \ - docs/implementation/audit-remediation-plan.md \ - internal/app/cli_output_witness_contract_test.go \ - internal/app/command_contract_generated.go \ - internal/app/invocation_profile_test.go \ - internal/command/requirementbrowser/requirementbrowser_test.go \ - internal/command/requirementbrowser/v1_adapter.go \ - internal/command/requirementcontext/v1_adapter.go \ - internal/command/requirementdiff/v1_adapter.go \ - internal/command/stackpreset/preset_ids_generated.go \ - internal/tools/commandcontractgen/condition_model.go \ - internal/tools/commandcontractgen/main.go \ - internal/tools/commandcontractgen/main_test.go \ - internal/tools/pythonpackage/continuation_test.go \ - release/change-record.v2.json \ - scripts/workflow_browser_runtime_oracle_test.go \ - scripts/workflow_oracle_support_test.go \ - scripts/workflow_runtime_preconditions_test.go \ - scripts/workflow_security_scanner_oracles_test.go \ - scripts/workflow_source_oracles_test.go \ - tests/browser/axe-harness.mjs | - LC_ALL=C sort - )" - actual_untracked_inventory="$( - git ls-files --others --exclude-standard | LC_ALL=C sort - )" - test "$expected_correction_path_inventory" = \ - "$correction_path_inventory" - test "$expected_untracked_inventory" = "$actual_untracked_inventory" - printf '%s\n' "$expected_correction_path_inventory" | - git add --pathspec-from-file=- - staged_path_inventory="$( - git diff --cached --name-only | LC_ALL=C sort - )" - actual_baseline_added_inventory="$( - git diff --cached --diff-filter=A --name-only "$baseline_sha" | - LC_ALL=C sort - )" - missing_decomposition_owner_inventory="$( - comm -23 \ - <(printf '%s\n' "$expected_decomposition_owner_inventory") \ - <(printf '%s\n' "$actual_baseline_added_inventory") - )" - test "$expected_correction_path_inventory" = "$staged_path_inventory" - test -z "$missing_decomposition_owner_inventory" - test "$expected_baseline_added_inventory" = \ - "$actual_baseline_added_inventory" - test -z "$(git diff --name-only)" - test -z "$(git ls-files --others --exclude-standard)" - ``` - - Any extra or missing path stops candidate creation. -2. Review the staged diff and generated-file freshness. -3. Create the first candidate with a conventional, human-oriented message, or - amend that commit without changing its reviewed message after a correction - epoch: - - ```text - fix: close audit remediation gaps - ``` - - ```bash - git commit --amend --no-edit - ``` - -4. Record the candidate commit SHA. -5. Require a clean worktree apart from ignored local artifacts. - -### P12.3 Committed-object validation epoch - -Against the candidate SHA: - -1. rerun `git diff --check` and `npm run check`; -2. ask three fresh read-only agents to validate the committed diff against the - design and plan; -3. require all three to return `APPROVE` with no unresolved confirmed finding; -4. after every confirmed finding from those reviews is corrected, run one - additional independent `gpt-5.6-sol` reviewer with maximum reasoning - effort against the exact candidate SHA and require no unresolved confirmed - finding; -5. set design and plan status to `implemented/validated` only within the same - candidate correction epoch; -6. if any correction or status edit changes bytes, amend the candidate commit, - record the new SHA, and repeat the entire full gate and three-agent review; - the independent maximum-reasoning audit must also be repeated when a - confirmed correction changes bytes; -7. accept exactly one final committed SHA as publication input and substitute - its literal 40-hex value for `` in every P12.4-P12.6 - command. Re-deriving it from a later mutable `HEAD` is forbidden. - -### P12.4 Publish or refresh the branch - -Verify the already published remote before replacing its reviewed candidate: - -```bash -set -euo pipefail -audit_baseline_sha="3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64" -integration_base_sha="0df4c28bac9737f476f7dc66030363b8b40d5417" -final_sha="" -previous_remote_sha="7e29a8e2a85d36a0c439753dfb5b951ed598078a" -test "$(git branch --show-current)" = "fix/audit-remediation" -test "$(git rev-parse HEAD)" = "$final_sha" -test "$(git rev-list --parents -n 1 "$final_sha")" = \ - "$final_sha $integration_base_sha" -test "$(git merge-base "$audit_baseline_sha" "$integration_base_sha")" = \ - "$audit_baseline_sha" -test "$(git remote get-url --push origin)" = \ - "https://github.com/research-engineering/agentic-proofkit" -test "$integration_base_sha" = \ - "$(git ls-remote origin refs/heads/main | awk '{print $1}')" -test "$previous_remote_sha" = \ - "$(git ls-remote origin refs/heads/fix/audit-remediation | awk '{print $1}')" -git push \ - --force-with-lease=refs/heads/fix/audit-remediation:"$previous_remote_sha" \ - origin "${final_sha}:refs/heads/fix/audit-remediation" -test "$final_sha" = \ - "$(git ls-remote origin refs/heads/fix/audit-remediation | awk '{print $1}')" -test "$integration_base_sha" = \ - "$(git ls-remote origin refs/heads/main | awk '{print $1}')" -git branch --set-upstream-to=origin/fix/audit-remediation \ - fix/audit-remediation -``` - -Any remote mismatch stops publication. - -### P12.5 Create or refresh the pull request - -Create a non-draft pull request into `main` if none exists. If pull request -`#78` already exists for the branch, update that same pull request rather than -creating a duplicate. - -Title: - -```text -Close proof, contract, release, and onboarding audit gaps -``` - -The body includes: - -- historical audit baseline, integration base, and current PR scope; -- adjudicated fixes and rejected hypotheses; -- intentional compatibility changes and `0.2.0` migration; -- design and plan review history; -- exact narrow and full gates; -- provider failure diagnosis, corrective cycles, and current check status; -- residual non-claims; -- the closed large-file ledger, the remediated workflow god-file - concentration, and the rejected premature merge/split candidates. - -Write the reviewed body to -`/tmp/agentic-proofkit-audit-remediation-pr-body.md`. For the existing pull -request, run: - -```bash -set -euo pipefail -integration_base_sha="0df4c28bac9737f476f7dc66030363b8b40d5417" -final_sha="" -repo="research-engineering/agentic-proofkit" -test "$integration_base_sha" = \ - "$(git ls-remote origin refs/heads/main | awk '{print $1}')" -pr_before_edit="$(mktemp)" -gh pr view 78 \ - --repo "$repo" \ - --json author,headRefName,headRefOid,baseRefName,baseRefOid,state,mergedAt \ - > "$pr_before_edit" -jq -e \ - --arg sha "$final_sha" \ - --arg base "$integration_base_sha" \ - '.author.login == "iperev" - and .headRefName == "fix/audit-remediation" - and .headRefOid == $sha - and .baseRefName == "main" - and .baseRefOid == $base - and .state == "OPEN" - and .mergedAt == null' \ - "$pr_before_edit" >/dev/null - -gh pr edit 78 \ - --repo "$repo" \ - --title "Close proof, contract, release, and onboarding audit gaps" \ - --body-file /tmp/agentic-proofkit-audit-remediation-pr-body.md -pr_after_edit="$(mktemp)" -gh pr view 78 \ - --repo "$repo" \ - --json url,title,author,headRefName,headRefOid,baseRefName,baseRefOid,isDraft,state,mergedAt,body \ - > "$pr_after_edit" -jq -e \ - --arg sha "$final_sha" \ - --arg base "$integration_base_sha" \ - '.author.login == "iperev" - and .headRefName == "fix/audit-remediation" - and .headRefOid == $sha - and .baseRefName == "main" - and .baseRefOid == $base - and .isDraft == false - and .state == "OPEN" - and .mergedAt == null - and .title == "Close proof, contract, release, and onboarding audit gaps"' \ - "$pr_after_edit" >/dev/null -jq -j '.body' "$pr_after_edit" > /tmp/proofkit-pr-body-readback.md -cmp -s /tmp/agentic-proofkit-audit-remediation-pr-body.md \ - /tmp/proofkit-pr-body-readback.md -``` - -Require `author.login=iperev`, `headRefName=fix/audit-remediation`, -`headRefOid=`, `baseRefName=main`, -`baseRefOid=0df4c28bac9737f476f7dc66030363b8b40d5417`, `isDraft=false`, -`state=OPEN`, `mergedAt=null`, and the exact reviewed title above. The refreshed -body must name the final SHA, tree, file and line counts, gate counts, and -provider disposition; old candidate identities or counts are forbidden. - -### P12.6 Provider status - -1. Read back the PR author, exact `headRefOid` and `baseRefOid`, head/base refs, - `state`, and `mergedAt`. -2. Confirm author is `iperev`. -3. Inspect provider checks and require exactly these source-owned closeout - tuples to be present for the final SHA in provider workflow attempt `1`: - - `ci` / `quality / source`; - - `ci` / `quality / platform smoke / macos-15`; - - `ci` / `quality / browser runtime`; - - `ci` / `quality / required aggregate`. - This is the closed inventory owned by `.github/workflows/ci.yml` and its - typed workflow oracle, not evidence of provider branch-protection settings. -4. Wait for terminal checks when available without converting pending or - skipped states into success. -5. If a browser failure moves between tests at the same timeout cap, retain and - inspect the first-failure trace before changing application behavior, - timeout policy, or retry policy; diagnostic reruns are not merge proof. -6. Do not merge unless the user separately authorizes merge. -7. Treat a required provider failure or an absent required check as a blocker, - including when the failure is terminal and fully diagnosed; do not retire - the design or plan while any such blocker exists. -8. After all checks triggered for the final SHA reach terminal states and every - required check passes, complete retrospective routing. -9. Execute the canonical provider-projection function from step 10 once, - construct the canonical closeout record from the exact local sources below, - compute both SHA-256 values, and refresh the reviewed pull-request body with - exact `provider-projection-sha256: sha256:` and - `closeout-record-sha256: sha256:` markers plus canonical JSON bytes - between their respective exact `*-json-begin` and `*-json-end` sentinel - lines. Human-readable summary text remains explicitly non-authoritative. -10. Perform the bounded authoritative readback below. The two identity - snapshots reject a persistent head mismatch at either boundary, while the - provider endpoints bind every conclusion read to the literal final SHA: - -```bash -set -euo pipefail -audit_baseline_sha="3d86b6d0e4ec4a6c6a7f7a35ff2787011771aa64" -integration_base_sha="0df4c28bac9737f476f7dc66030363b8b40d5417" -final_sha="" -repo="research-engineering/agentic-proofkit" -branch_ref="refs/heads/fix/audit-remediation" -base_ref="refs/heads/main" -expected_title="Close proof, contract, release, and onboarding audit gaps" -pr_snapshot="$(mktemp)" -ci_runs="$(mktemp)" -ci_jobs="$(mktemp)" -check_runs="$(mktemp)" -commit_status="$(mktemp)" -provider_projection_one="$(mktemp)" -provider_projection_two="$(mktemp)" -provider_projection_embedded_one="$(mktemp)" -provider_projection_embedded_server="$(mktemp)" -closeout_record_expected="$(mktemp)" -closeout_record_embedded_one="$(mktemp)" -closeout_record_embedded_server="$(mktemp)" -coverage_snapshot="$(mktemp)" -browser_snapshot="$(mktemp)" -package_execution_snapshot="$(mktemp)" -local_closeout_report="$(mktemp)" -reviewed_body_snapshot="$(mktemp)" -pr_body_readback="$(mktemp)" -test "$final_sha" = "$(git rev-parse HEAD)" -test -z "$(git status --porcelain)" -final_tree="$(git rev-parse "$final_sha^{tree}")" -diff_stats="$( - git diff --numstat "$integration_base_sha" "$final_sha" | - awk ' - { - if ($1 !~ /^[0-9]+$/ || $2 !~ /^[0-9]+$/) { - invalid = 1 - } - files++ - added += $1 - deleted += $2 - } - END { - if (invalid) { - exit 1 - } - print files + 0, added + 0, deleted + 0 - } - ' -)" -read -r diff_file_count diff_added_lines diff_deleted_lines \ - <<< "$diff_stats" -cp artifacts/proofkit/coverage-metrics.json "$coverage_snapshot" -cp artifacts/proofkit/browser-runtime-proof.json "$browser_snapshot" -cp artifacts/proofkit/package-artifact-execution.json \ - "$package_execution_snapshot" -assert_coverage_snapshot() { - jq -e -s \ - --arg sha "$final_sha" ' - length == 1 - and (.[0] | - .provenance.sourceRevision == $sha - and .requirements.totalRecords == 69 - and .proofBindings.boundRequirementCount == 69 - and .proofBindings.scenarioCount == 173 - and .commandRoutes.commandCount == 78 - ) - ' >/dev/null -} -assert_coverage_snapshot < "$coverage_snapshot" -for rejected_scenario_count in 167 172 174; do - if jq -c \ - --argjson count "$rejected_scenario_count" \ - '.proofBindings.scenarioCount = $count' \ - "$coverage_snapshot" | - assert_coverage_snapshot; then - exit 1 - fi -done -jq -e -s \ - --arg sha "$final_sha" ' - length == 1 - and (.[0] | - .sourceRevision == $sha - and .sourceTreeState == "clean" - and .state == "passed" - and (.projects | length) == 3 - and (.projects | map(.name) | sort) - == ["chromium", "firefox", "webkit"] - and (.projects | all( - .executedTestCount == 31 - and .passedTestCount == 31 - )) - ) -' "$browser_snapshot" >/dev/null -jq -e -s \ - --arg sha "$final_sha" ' - length == 1 - and (.[0] | - .commandId == "proofkit.package-artifact" - and .sourceRevision == $sha - and .status == "passed" - and .exitCode == 0 - ) -' "$package_execution_snapshot" >/dev/null -go run ./internal/tools/releasecloseoutinput | - go run ./cmd/agentic-proofkit completion-criteria --input - \ - > "$local_closeout_report" -jq -e -s ' - length == 1 - and (.[0] | - .state == "passed" - and .summary.blockingCriterionCount == 5 - and .summary.blockingUnsatisfiedCount == 0 - and .summary.advisoryCriterionCount == 1 - and .summary.statusCounts.satisfied == 5 - and .summary.statusCounts.advisory_skipped == 1 - ) -' "$local_closeout_report" >/dev/null -jq -S -n \ - --arg finalSha "$final_sha" \ - --arg finalTree "$final_tree" \ - --arg auditBaselineSha "$audit_baseline_sha" \ - --arg integrationBaseSha "$integration_base_sha" \ - --argjson diffFiles "$diff_file_count" \ - --argjson diffAdded "$diff_added_lines" \ - --argjson diffDeleted "$diff_deleted_lines" \ - --slurpfile coverage "$coverage_snapshot" \ - --slurpfile browser "$browser_snapshot" \ - --slurpfile packageExecution "$package_execution_snapshot" \ - --slurpfile closeout "$local_closeout_report" \ - '{ - schemaVersion: 1, - finalSha: $finalSha, - finalTree: $finalTree, - auditBaselineSha: $auditBaselineSha, - integrationBaseSha: $integrationBaseSha, - diff: { - files: $diffFiles, - addedLines: $diffAdded, - deletedLines: $diffDeleted - }, - localGates: { - packageArtifact: { - commandId: $packageExecution[0].commandId, - sourceRevision: $packageExecution[0].sourceRevision, - status: $packageExecution[0].status, - exitCode: $packageExecution[0].exitCode - }, - browserRuntime: { - sourceRevision: $browser[0].sourceRevision, - sourceTreeState: $browser[0].sourceTreeState, - state: $browser[0].state, - projectCount: ($browser[0].projects | length), - executedTestCount: ( - $browser[0].projects | map(.executedTestCount) | add - ), - passedTestCount: ( - $browser[0].projects | map(.passedTestCount) | add - ) - }, - coverage: { - sourceRevision: $coverage[0].provenance.sourceRevision, - requirementCount: $coverage[0].requirements.totalRecords, - boundRequirementCount: - $coverage[0].proofBindings.boundRequirementCount, - scenarioCount: $coverage[0].proofBindings.scenarioCount, - commandCount: $coverage[0].commandRoutes.commandCount - }, - localCloseout: { - state: $closeout[0].state, - blockingCriterionCount: - $closeout[0].summary.blockingCriterionCount, - blockingUnsatisfiedCount: - $closeout[0].summary.blockingUnsatisfiedCount, - satisfiedCount: $closeout[0].summary.statusCounts.satisfied, - advisorySkippedCount: - $closeout[0].summary.statusCounts.advisory_skipped - } - }, - residualNonClaims: [ - "No registry publication, Trusted Publisher or OIDC provider identity, branch-protection setting, rollout, deployment, or production readiness is proven.", - "Pinned browser engines do not imply complete WCAG conformance or branded Safari parity.", - "The output writer does not claim protection from same-user content or namespace mutation during the operation, fsync durability, or a repository-wide transaction.", - "Local closeout evidence objects are individually admitted snapshots, not an atomic filesystem transaction across all artifacts.", - "Provider observations are bounded but not atomic across endpoints or immutable after the final response." - ], - retrospective: [ - "Single-factor Firefox hypotheses were falsified before the combined axe-initialization and trace-screenshot correction was admitted.", - "Repeated closeout misses require literal-SHA provider APIs, mutation oracles at irreversible boundaries, admitted byte snapshots, and machine-readable closeout projections." - ] - }' > "$closeout_record_expected" -test "$final_sha" = "$(git rev-parse HEAD)" -test -z "$(git status --porcelain)" -cp /tmp/agentic-proofkit-audit-remediation-pr-body.md \ - "$reviewed_body_snapshot" -reviewed_body_digest="sha256:$( - shasum -a 256 "$reviewed_body_snapshot" | awk '{print $1}' -)" - -assert_remote_and_pr_identity() { - test "$final_sha" = \ - "$(git ls-remote origin "$branch_ref" | awk '{print $1}')" - test "$integration_base_sha" = \ - "$(git ls-remote origin "$base_ref" | awk '{print $1}')" - gh pr view 78 --repo "$repo" \ - --json title,author,headRefName,headRefOid,baseRefName,baseRefOid,isDraft,state,mergedAt,body \ - > "$pr_snapshot" - jq -e \ - --arg sha "$final_sha" \ - --arg base "$integration_base_sha" \ - --arg title "$expected_title" \ - '.author.login == "iperev" - and .headRefName == "fix/audit-remediation" - and .headRefOid == $sha - and .baseRefName == "main" - and .baseRefOid == $base - and .isDraft == false - and .state == "OPEN" - and .mergedAt == null - and .title == $title' \ - "$pr_snapshot" >/dev/null -} - -assert_provider_disposition() { -local projection_path="$1" -gh api \ - "repos/$repo/actions/workflows/ci.yml/runs?event=pull_request&head_sha=$final_sha&per_page=100" \ - > "$ci_runs" -ci_run_id="$( - jq -er \ - --arg sha "$final_sha" \ - --arg base "$integration_base_sha" \ - '. as $response - | select( - $response.total_count == ($response.workflow_runs | length) - and $response.total_count > 0 - ) - | [ - $response.workflow_runs[] - | select( - .head_sha == $sha - and .head_branch == "fix/audit-remediation" - and .event == "pull_request" - and (.pull_requests | length) == 1 - and .pull_requests[0].number == 78 - and .pull_requests[0].head.ref == "fix/audit-remediation" - and .pull_requests[0].head.sha == $sha - and .pull_requests[0].head.repo.url - == "https://api.github.com/repos/research-engineering/agentic-proofkit" - and .pull_requests[0].base.ref == "main" - and .pull_requests[0].base.sha == $base - and .pull_requests[0].base.repo.url - == "https://api.github.com/repos/research-engineering/agentic-proofkit" - ) - ] - | if length != 1 then error("expected one exact-PR exact-SHA ci run") - else .[0] - end - | select( - .run_attempt == 1 - and .status == "completed" - and .conclusion == "success" - ) - | .id' \ - "$ci_runs" -)" -ci_run_attempt="$( - jq -er \ - --argjson id "$ci_run_id" \ - '.workflow_runs[] - | select(.id == $id) - | .run_attempt' \ - "$ci_runs" -)" -gh api \ - "repos/$repo/actions/runs/$ci_run_id/attempts/$ci_run_attempt/jobs?per_page=100" \ - > "$ci_jobs" -jq -e ' - [ - "quality / source", - "quality / platform smoke / macos-15", - "quality / browser runtime", - "quality / required aggregate" - ] as $required - | .jobs as $jobs - | ($jobs | length) == ($required | length) - and ([$jobs[].name] | sort) == ($required | sort) - and ($jobs | all( - .head_sha == $sha - and .run_attempt == $attempt - and .status == "completed" - and .conclusion == "success" - )) -' \ - --arg sha "$final_sha" \ - --argjson attempt "$ci_run_attempt" \ - "$ci_jobs" >/dev/null -gh api \ - "repos/$repo/commits/$final_sha/check-runs?filter=all&per_page=100" \ - > "$check_runs" -jq -e ' - .total_count == (.check_runs | length) - and (.check_runs | all(.status == "completed")) -' "$check_runs" >/dev/null -gh api \ - "repos/$repo/commits/$final_sha/status?per_page=100" \ - > "$commit_status" -jq -e \ - --arg sha "$final_sha" \ - '.sha == $sha - and (.statuses | length) < 100 - and (.statuses | all( - .state | IN("success", "failure", "error") - ))' \ - "$commit_status" >/dev/null -jq -S -n \ - --arg finalSha "$final_sha" \ - --arg integrationBaseSha "$integration_base_sha" \ - --argjson ciRunId "$ci_run_id" \ - --slurpfile runs "$ci_runs" \ - --slurpfile jobs "$ci_jobs" \ - --slurpfile checks "$check_runs" \ - --slurpfile statuses "$commit_status" \ - '{ - schemaVersion: 1, - finalSha: $finalSha, - integrationBaseSha: $integrationBaseSha, - ciRun: ( - $runs[0].workflow_runs[] - | select(.id == $ciRunId) - | { - id, - runAttempt: .run_attempt, - event, - status, - conclusion, - headSha: .head_sha, - headBranch: .head_branch, - pullRequests: [ - .pull_requests[] - | { - number, - headRef: .head.ref, - headSha: .head.sha, - headRepo: .head.repo.url, - baseRef: .base.ref, - baseSha: .base.sha, - baseRepo: .base.repo.url - } - ] - } - ), - jobs: ( - $jobs[0].jobs - | map({ - id, - name, - status, - conclusion, - headSha: .head_sha, - runAttempt: .run_attempt - }) - | sort_by(.name, .id) - ), - checkRuns: ( - $checks[0].check_runs - | map({ - id, - name, - status, - conclusion, - appSlug: .app.slug, - externalId: .external_id - }) - | sort_by(.name, .id) - ), - legacyStatuses: ( - $statuses[0].statuses - | map({ - id, - context, - state, - description, - targetUrl: .target_url, - creatorLogin: .creator.login - }) - | sort_by(.context, .id) - ) - }' > "$projection_path" -} - -assert_embedded_projection() { - local body_path="$1" - local projection_path="$2" - local embedded_path="$3" - local marker_name="$4" - local begin_line="$5" - local end_line="$6" - local projection_digest - projection_digest="sha256:$( - shasum -a 256 "$projection_path" | awk '{print $1}' - )" - test "$( - grep -Ec "^${marker_name}:" "$body_path" - )" -eq 1 - test "$( - grep -Ec \ - "^${marker_name}: sha256:[0-9a-f]{64}$" \ - "$body_path" - )" -eq 1 - grep -Fqx \ - "${marker_name}: $projection_digest" \ - "$body_path" - awk \ - -v begin_line="$begin_line" \ - -v end_line="$end_line" ' - BEGIN { - begins = 0 - ends = 0 - inside = 0 - invalid = 0 - } - $0 == begin_line { - begins++ - if (inside || ends > 0) { - invalid = 1 - } - inside = 1 - next - } - $0 == end_line { - ends++ - if (!inside) { - invalid = 1 - } - inside = 0 - next - } - inside { - print - } - END { - if (invalid || begins != 1 || ends != 1 || inside) { - exit 1 - } - } - ' "$body_path" > "$embedded_path" - cmp -s "$projection_path" "$embedded_path" -} - -assert_remote_and_pr_identity -assert_provider_disposition "$provider_projection_one" -assert_embedded_projection \ - "$reviewed_body_snapshot" \ - "$provider_projection_one" \ - "$provider_projection_embedded_one" \ - "provider-projection-sha256" \ - "provider-projection-json-begin" \ - "provider-projection-json-end" -assert_embedded_projection \ - "$reviewed_body_snapshot" \ - "$closeout_record_expected" \ - "$closeout_record_embedded_one" \ - "closeout-record-sha256" \ - "closeout-record-json-begin" \ - "closeout-record-json-end" -assert_remote_and_pr_identity -assert_provider_disposition "$provider_projection_two" -cmp -s "$provider_projection_one" "$provider_projection_two" -assert_remote_and_pr_identity -jq -j '.body' "$pr_snapshot" > "$pr_body_readback" -test "$reviewed_body_digest" = "sha256:$( - shasum -a 256 "$reviewed_body_snapshot" | awk '{print $1}' -)" -cmp -s "$reviewed_body_snapshot" "$pr_body_readback" -assert_embedded_projection \ - "$pr_body_readback" \ - "$provider_projection_two" \ - "$provider_projection_embedded_server" \ - "provider-projection-sha256" \ - "provider-projection-json-begin" \ - "provider-projection-json-end" -assert_embedded_projection \ - "$pr_body_readback" \ - "$closeout_record_expected" \ - "$closeout_record_embedded_server" \ - "closeout-record-sha256" \ - "closeout-record-json-begin" \ - "closeout-record-json-end" -``` - -Each provider pass applies the following rules. The workflow-run query is -filtered by the literal final SHA and `ci.yml`; the -filtered set must contain exactly one run bound uniquely to pull request `#78`, -the exact `fix/audit-remediation` head in this repository, and the `main` base -in this repository. Multiple runs for the same PR and SHA fail closed. The sole -run must itself pass, and its exact current attempt must equal `1` and contain -only the four closed-inventory jobs, each bound to that SHA, attempt `1`, and -concluded `success`; a provider rerun or repeated same-object run is diagnostic -evidence only. The commit check-run endpoint independently -requires every triggered check run for the literal SHA to be terminal and -fails closed if pagination would hide a row. The literal-SHA combined-status -endpoint rejects pending legacy contexts, admits terminal `success`, `failure`, -and `error` only for accurate reporting, and fails closed at the page limit. -The first pass emits a canonical projection of every provider -decision-relevant field. Each local artifact used by the closeout record is -copied once to a private file; validation and projection consume only those -same snapshot bytes, after which final `HEAD` and tracked-tree cleanliness are -rechecked. Only fields named by the exact validation predicates are projected; -opaque artifact digests are omitted. The snapshots are individually admitted -and do not claim an atomic filesystem transaction across all artifacts. Before -this final block, use an -earlier execution of the same function and the exact local snapshots admitted -above to put both the provider projection and canonical closeout record into -the reviewed local -pull-request body with their exact digest markers and sentinel pairs. The -closeout record machine-binds the final tree, diff counts, local gate counts, -residual non-claims, and retrospective instead of relying on narrative -presence. At block entry, copy those reviewed bytes once into a private -snapshot and bind that snapshot to its digest; the mutable fixed pathname is -never read again. The block rejects duplicate or malformed markers and -sentinels in that snapshot, extracts both embedded records, and requires byte -equality before requiring the second provider projection to be byte-identical. -The final server body is then compared with the unchanged reviewed snapshot -and independently subjected to the same marker, sentinel, digest, and -embedded-byte validation for both records. Human-readable summary prose is -explicitly non-authoritative. Thus both admitted local bytes and observed -server bytes are bound directly to the stable provider disposition and exact -closeout facts rather than merely asserting that some terminal predicate -passed. -The final identity snapshot is the one used for byte-transparent body -comparison and direct server-body validation. The complete provider pass is -executed twice, with identity checks before, between, and after the passes; -persistent provider reruns or new nonterminal rows therefore invalidate the -second pass. The observations are not an atomic snapshot across endpoints. -Concurrent `A -> B -> A` PR identity history between observations and provider -mutation after the final response remain explicit non-claims, but neither can -substitute provider conclusions for `A` because every provider query is bound -directly to `final_sha`. This final readback must require the exact final SHA, -tree, gate counts, bounded provider observations, residual non-claims, and -retrospective result. It is the final closeout projection and the last -retirement precondition. - -## Completion criteria - -The implementation is complete only if: - -1. all design findings R-01 through R-29 and R-11a through R-11d have their - exact durable witness routes; -2. every current-wrong counterexample is observed red before repair or is - otherwise preserved as an isolated mutation proof; -3. every narrow gate and `npm run check` pass on the final committed object; -4. all three final reviewers approve and the independent maximum-reasoning - audit reports no unresolved confirmed finding; -5. business-logic changes are limited to the design's intentional compatibility - list; -6. GitHub identity and push permission are reverified; -7. the branch is pushed and the PR authored by `iperev` is open, non-draft, - unmerged, and bound to the literal final SHA and reviewed integration-base - SHA; -8. in two consecutive final literal-SHA provider passes, every tuple in the - closed source-owned required-check inventory is present with a `success` - conclusion, every other observed check is terminal, and all states are - reported without claiming an atomic cross-endpoint snapshot; -9. retrospective routing is completed; -10. the final pull-request closeout projection is read back with the exact - provider disposition, residual non-claims, and retrospective result, while - bracketing identity snapshots both bind the remote head/base branches and - pull request to the literal final and integration-base SHAs and the - server-side - body equals the reviewed body and uniquely contains both the SHA-256 and - exact sentinel-delimited bytes of the byte-identical canonical provider - projections; -11. temporary design/plan retirement conditions are explicit and remain false - until criteria 1 through 10 all hold. - -## Review acceptance criteria - -Reviewers approve this plan only if: - -- every step is executable and ordered after its dependencies; -- every gate selector matches the test it claims to run; -- no production change precedes its owner and counterexample; -- no plan step invents provider, registry, or selective proof; -- the shared-worktree execution model preserves reviewer independence; -- the final publication account and permissions are explicit; -- the plan can be overturned safely at each named rollback condition. diff --git a/docs/release-process.md b/docs/release-process.md index 0520115..0c2b663 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -42,10 +42,11 @@ Go source -> GitHub Release assets with checksums and SBOM for provenance lookup ``` -The committed `release/change-record.v2.json` owns the reviewed, version-bound -declaration of the public-contract delta, migration decision, platform -requirements, known limitations, and rollback strategy. It does not infer -change completeness from the source diff. The repository-owned +In a source checkout, the committed `release/change-record.v2.json` owns the +reviewed, version-bound declaration of the public-contract delta, migration +decision, platform requirements, known limitations, and rollback strategy. It +is not part of the installed npm or PyPI projection and does not infer change +completeness from the source diff. The repository-owned `release:manifest` tool admits that record and creates `release-manifest.json`, `checksums.sha256`, `metadata-checksums.sha256`, `sbom-subjects.sha256`, release notes, and deterministic SBOM candidate evidence from explicit package, @@ -81,10 +82,10 @@ Before publishing a version: 1. The source tree is clean. 2. `package.json` contains the exact new version. -3. `release/change-record.v2.json` contains the same version, names the exact - previous version and compatible or breaking change class, and explicitly - classifies breaking changes, additions, migration, platform requirements, - known limitations, and rollback. +3. In the source checkout, `release/change-record.v2.json` contains the same + version, names the exact previous version and compatible or breaking change + class, and explicitly classifies breaking changes, additions, migration, + platform requirements, known limitations, and rollback. 4. `package.json` repository, license, bin, exports, files, and publishConfig match the intended public package contract. 5. The npm account has verified email and write-protective 2FA, or the package diff --git a/internal/app/cli_abi_test.go b/internal/app/cli_abi_test.go index 9803f48..4818aff 100644 --- a/internal/app/cli_abi_test.go +++ b/internal/app/cli_abi_test.go @@ -1046,7 +1046,7 @@ func TestRequirementBrowserServerSpecTreeCLIABI(t *testing.T) { func TestRequirementBrowserOneShotCLIOutputVariants(t *testing.T) { if runtime.GOOS == "windows" { - t.Skip("test launcher fixture is POSIX-only") + t.Fatal("test launcher fixture is POSIX-only") } fixture, err := browserfixture.Workspace() if err != nil { diff --git a/internal/app/cli_contract_test.go b/internal/app/cli_contract_test.go index e0c61a6..e2aa84a 100644 --- a/internal/app/cli_contract_test.go +++ b/internal/app/cli_contract_test.go @@ -22,7 +22,7 @@ import ( ) const ( - cliContractPublicABISHA256 = "1b615f82dd66b83cf3d1a72154d7f4b7d1946eb3a6727cf4d0c54f065462c63f" + cliContractPublicABISHA256 = "4f305627f56ea031fc501b872ce9e20afa3e6b7b3af4fe20c48f787693198fa4" maxAggregateFileReadBytesForContractTest = 64 << 20 maxPackageManifestBytesForContractTest = 256 << 10 maxSourceFileBytesForContractTest = 8 << 20 @@ -156,6 +156,61 @@ func TestCLIContractsAreCompleteGeneratedAndWitnessBound(t *testing.T) { } } +func TestCLIContractInputRootShapesMatchNativeOwnerVariants(t *testing.T) { + contract := readCLIContract(t) + definitions := cliContractDefinitionMap(t, contract.ContractDefinitions) + tests := []struct { + definitionID string + allowed []string + required []string + }{ + { + definitionID: "proofkit.external-consumer.input.v1.root-shape", + allowed: []string{"evidence", "input", "schemaVersion"}, + required: []string{"evidence", "input", "schemaVersion"}, + }, + { + definitionID: "proofkit.registry-consumer.input.v1.root-shape", + allowed: []string{"input", "proof", "schemaVersion"}, + required: []string{"input", "schemaVersion"}, + }, + { + definitionID: "proofkit.requirement-proof-source-set.input.v1.root-shape", + allowed: []string{"canonicalEnvelope", "projection", "sourceSet", "sources"}, + required: []string{"canonicalEnvelope", "sourceSet", "sources"}, + }, + { + definitionID: "proofkit.secret-scan.input.v1.root-shape", + allowed: []string{"files", "nonClaims", "reportId", "schemaVersion", "suppressions"}, + required: []string{"files", "nonClaims", "reportId", "schemaVersion"}, + }, + { + definitionID: "proofkit.selective-gate-obligation-decision-input.input.v1.root-shape", + allowed: []string{"commandRoutes", "decisionId", "evidence", "nonClaims", "receiptCurrentnessScopeAdmission", "receiptTrustClassAdmission", "schemaVersion"}, + required: []string{"commandRoutes", "decisionId", "evidence", "nonClaims", "schemaVersion"}, + }, + } + for _, test := range tests { + t.Run(test.definitionID, func(t *testing.T) { + definition := definitions[test.definitionID] + if definition == nil { + t.Fatalf("missing definition %s", test.definitionID) + } + variants := definition["fieldTree"].(map[string]any)["variants"].([]any) + if len(variants) != 1 { + t.Fatalf("variants=%d want 1", len(variants)) + } + variant := variants[0].(map[string]any) + if actual := stringsFromAny(variant["allowedFields"].([]any)); !slices.Equal(actual, test.allowed) { + t.Fatalf("allowedFields=%v want %v", actual, test.allowed) + } + if actual := stringsFromAny(variant["requiredFields"].([]any)); !slices.Equal(actual, test.required) { + t.Fatalf("requiredFields=%v want %v", actual, test.required) + } + }) + } +} + func TestCLIContractRootShapeVariantInventoryIsClosedAndModeComplete(t *testing.T) { contract := readCLIContract(t) definitions := cliContractDefinitionMap(t, contract.ContractDefinitions) diff --git a/internal/app/command_contract_generated.go b/internal/app/command_contract_generated.go index 1ca60c6..17b504b 100644 --- a/internal/app/command_contract_generated.go +++ b/internal/app/command_contract_generated.go @@ -1,7 +1,7 @@ // Code generated by internal/tools/commandcontractgen; DO NOT EDIT. package app -const commandContractSourceSHA256 = "90025ee656c5d90b1909423776121f12ce28b74b52d0eae2c34dd1bc55d2c6e9" +const commandContractSourceSHA256 = "611956ab74fd69cdede8fd37947c6c7ed05f3bd95ad6cc2f5249cbddf777d7d3" type generatedCommandContractMetadata struct { InputContractSHA256 string @@ -26,7 +26,7 @@ var generatedCommandContractMetadataByName = map[string]generatedCommandContract "deployment-evidence-admission": {InputContractSHA256: "sha256:c3472b87497be14c107dcf95f2c98594d21ec88e514919fe8fb7da858206421d", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.deployment-evidence-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:818fb2b7dbf67be1da27cc62e78c7bd7fdaddaa38fa5ec80f0cad64fbcb60d90", FlagChoices: map[string][]string{}}, "document-lifecycle-boundary": {InputContractSHA256: "sha256:393836f0ce8e23df351e01d7dec221908ae76fa9b1a045b96fce229eecc844ed", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.document-lifecycle-boundary.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:65c7c0338df60d83d71df9fce3a7e60ce0b0d86819a38e2a6ffeac58b4abc934", FlagChoices: map[string][]string{}}, "evidence-graph": {InputContractSHA256: "sha256:82c4f14a91ae1b77d1e338e4a1390eec45093cb184830c5dab11e0d2d192c55e", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.evidence-graph.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:254ee56ab897df021856db23b62e9366ed2dfe26b821efa0da9eff4ef83a2592", FlagChoices: map[string][]string{}}, - "external-consumer": {InputContractSHA256: "sha256:0e820ed1e4ecfbfb2a9b66e11f883511be5d18e501f641c9d3f3366303508d00", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.external-consumer.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:2615a60f4cd21a865ed604aca94bf5066adb4bc8f7a3a2345bd53d181429822f", FlagChoices: map[string][]string{}}, + "external-consumer": {InputContractSHA256: "sha256:195d4f09c6fb03004f5ba3d6bad9aea7728ecd9aa876e7e30d521ff961a3e931", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.external-consumer.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:2615a60f4cd21a865ed604aca94bf5066adb4bc8f7a3a2345bd53d181429822f", FlagChoices: map[string][]string{}}, "gradual-adoption": {InputContractSHA256: "sha256:b82598422d2e18c7160d827836e964db6e9b0c2b14a119c1d4f5c4d18779c17f", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.gradual-adoption.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:75310c4f53b1fd3838d03e8988e7eb5faef61e757149a6f556033e23ddc488ff", FlagChoices: map[string][]string{}}, "gradual-adoption-bootstrap": {InputContractSHA256: "sha256:453bab6dc3f3c84ab9cd7d375966b1cd881b53f8e8414da4aa837f8215db6f19", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.gradual-adoption-bootstrap.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c00c79676575f2c5903321e87b4b494a3b6d77219b19552701cecde469db5427", FlagChoices: map[string][]string{}}, "gradual-adoption-guidance": {InputContractSHA256: "sha256:4752cbac81c864cb3e18a39facfd666a9707314233d54798c7f71e67d7f2800c", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.gradual-adoption-guidance.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:171fed4bb8d32a47fc5ec49796f5b0b55ed666feaccc2fbbfeb12da31d80ecc9", FlagChoices: map[string][]string{}}, @@ -38,30 +38,30 @@ var generatedCommandContractMetadataByName = map[string]generatedCommandContract "migration-plan": {InputContractSHA256: "sha256:58a62759a634101ce2ca9218184175134bbe5633328e1b23797b94c19fc9b11a", InputSchemaSummary: []string{"schemaVersion=1", "migrationId", "sourceProofOwners[]", "targetProofkitRefs[]", "parityEvidenceRefs[]", "retainedOwners[]", "retirementCandidates[]", "followUpCommands[]", "nonClaims[]", "root-shape-only definition proofkit.migration-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f14f0381e9dc241357c346315b95b03ef5b23f1d1bbc3b00f111fbe1515ed3ff", FlagChoices: map[string][]string{}}, "obligation-decision": {InputContractSHA256: "sha256:48d5110ff6f50ddda69bd92fbb47014c835dba6cd36b0ea1751b178619390e18", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.obligation-decision.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:dde199c92f94ea76491a84900ed7803dd534db4df6002792a174dafbddc00889", FlagChoices: map[string][]string{}}, "package-runtime-dependency-admission": {InputContractSHA256: "sha256:fc85887af9b8fcd899d245f0db30b2f2f68609822fc268126bf999082bb4115f", InputSchemaSummary: []string{"schemaVersion=1", "reportId", "expectedDependencySpec", "expectedLockfileIntegrity", "expectedPackageName", "expectedPackageVersion", "admissibleLocations{}", "packageResolution{}", "nonClaims[]", "root-shape-only definition proofkit.package-runtime-dependency-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c012032e8c8212fd50bc2e85669cc610609ca2124ebc992c9e88f44a1ad2d5fc", FlagChoices: map[string][]string{}}, - "pilot-admission": {InputContractSHA256: "sha256:6c4d6fb7ba99cb8da806826584807a723891955c83c04d0054a848e303e5ca9a", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.pilot-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:34d2ef2405b2a96937ec9527fed0abbee05d6aa8e41c27bf57512ac3ae44850d", FlagChoices: map[string][]string{}}, + "pilot-admission": {InputContractSHA256: "sha256:6c4d6fb7ba99cb8da806826584807a723891955c83c04d0054a848e303e5ca9a", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.pilot-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:2c48fabddcd739ec46573345862d668fb6b48c67c9a20a14f19f72ac53ddeb1d", FlagChoices: map[string][]string{}}, "producer-policy-self-proof": {InputContractSHA256: "sha256:d48e18826000c8d415f3c44b6c686e1da6ed962ef7ca36c9f705de8c68d034f9", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.producer-policy-self-proof.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:e82a3989a743f8babc6069f7af82b1dd1ea62bad8dbb18d95e105b36f74e4276", FlagChoices: map[string][]string{}}, "proof-obligation-algebra": {InputContractSHA256: "sha256:e8b03035a81579d03e1c7084999cdf2104fa4befa3ab7eecf1ae20db995f5f80", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.proof-obligation-algebra.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:2925965910426487ff135f16d20e654e2be5a880960ffeb62a83773ae0297c15", FlagChoices: map[string][]string{}}, "proof-receipt-admission": {InputContractSHA256: "sha256:7cb4c4fb60c8b5a37109bbd8c00d567749f7d181bbc905d8bc58155f139c44cb", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.proof-receipt-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:3f802ac3fac6762ede51f0e0a151f16dc10b4a20344a3887b3ee8bae43ce94f2", FlagChoices: map[string][]string{}}, "proof-slice": {InputContractSHA256: "sha256:c6abf98e38371a2afdd005b7d17317aa97fbae7a5aa9672367ee8ea4a53b9258", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.proof-slice.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:73e5f9db8bf966c9818b6581b10c6b20167af4ab1c4b33fa619c040a165125c7", FlagChoices: map[string][]string{}}, - "readiness-closeout": {InputContractSHA256: "sha256:d3a0a2b44372b5970afac5f2ed273dd5e315e9afb58930ecaffee4c3e92e67f8", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.readiness-closeout.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f51a259cb1e4c1bcaa1de7eb7beec3b0fbd1bd7d4b62d33a74623542f8e4fc37", FlagChoices: map[string][]string{}}, + "readiness-closeout": {InputContractSHA256: "sha256:4f427c1d0cefb00133d0d9fdb15f75ca9d12a26746632914e82bf72710883b9b", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.readiness-closeout.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:427a227aa59d60739bc7bdea03363ece95070520dc154fff1f063e174027cc5e", FlagChoices: map[string][]string{}}, "receipt-currentness-scope": {InputContractSHA256: "sha256:b581dbabdb74baf30093544409a8d9b467aed4b69695bfe0ac3b9ab005913a2d", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.receipt-currentness-scope.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:3b51b4b58ca5080e2dd6e3674f24710b7d0b8e6463b592f4521b160a42709cc4", FlagChoices: map[string][]string{}}, "receipt-producer-admission": {InputContractSHA256: "sha256:676aa03b2331a094e287dd3f2dfad3a74403b6f8dadb17e420b446aac0b3592c", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.receipt-producer-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:626b910bb8110901d6769c0ec2a14d216d9ca422aaf2f73ef5fc851c60b0847a", FlagChoices: map[string][]string{}}, "receipt-trust-class": {InputContractSHA256: "sha256:abc502cf3ac2553d6c2f3b57011af8cfb137b4071fb46f973ab72de917aabf50", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.receipt-trust-class.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:31f36036e665b53b5bbb10eb2843e7a0937fd5c2926860d8337c2f880a5dc7a3", FlagChoices: map[string][]string{}}, - "registry-consumer": {InputContractSHA256: "sha256:8b19d55503aeed6359fe6def27cdff1bcd097820a1a4c6ea137e5d151b49b7be", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.registry-consumer.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:0b557f1db529d4527807513a97ebaedc4ae0b3d61b4445647ccd77667674db4b", FlagChoices: map[string][]string{}}, + "registry-consumer": {InputContractSHA256: "sha256:b4c71b63507b262b84d510573aa094592ea94579c4f332833f787f1719fa012b", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.registry-consumer.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:0b557f1db529d4527807513a97ebaedc4ae0b3d61b4445647ccd77667674db4b", FlagChoices: map[string][]string{}}, "registry-consumer-proof-input-compose": {InputContractSHA256: "sha256:80bcaf6de948af9087e886dbf352e071fc49b98c2d4409250bfdf51ed19e84b3", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.registry-consumer-proof-input-compose.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:7dc6c11bde951ae90d54779759b9625751983a7a3aaae02006c9e7747ff149db", FlagChoices: map[string][]string{}}, "release-authority": {InputContractSHA256: "sha256:807f53ceab20f949fda99c10448f8490e16b22ae5b8915c9f471337321f5ffef", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.release-authority.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:7b01b165b79c37bbee95f272b2ab6e7707ec9c3fd542a2a5a820b676810adb86", FlagChoices: map[string][]string{}}, "rendered-artifact-freshness": {InputContractSHA256: "sha256:90961665c3b781202babf99b06743cb04e84a09a27bb4a967ce2cde965312f32", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.rendered-artifact-freshness.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:e5227c6c50f175c0c0b98395a8d53e8c582a4ffa51d5744e1c175e56147a15d7", FlagChoices: map[string][]string{}}, - "repo-profile-admission": {InputContractSHA256: "sha256:203a0be8c1fde0d39809b687e012b4aff3b656be0ddc5b93ccd75de7777448a6", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.repo-profile-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c761a9a6b5bb0aa1344eabb281985823daa7ef19784cf59634dc17cdea06d4a6", FlagChoices: map[string][]string{}}, + "repo-profile-admission": {InputContractSHA256: "sha256:3a7331d66195dbdc9f672d380efe8fdb9d1d2e36a764b8bc912dccdd81b0e965", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.repo-profile-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:36d2116fa144aa86d7b9d0ac59b89ad04fb97c85a11f3f65efb7311506761fbd", FlagChoices: map[string][]string{}}, "requirement-authoring-plan": {InputContractSHA256: "sha256:9167f6ea1e888c196c14799f30a7657cf7e74a4af53f15ef61b44b9ae82cc5ba", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-authoring-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:6cca126c3295537f0c7a73892e8c8778bbd10e442290e75dd45b833373312a46", FlagChoices: map[string][]string{}}, "requirement-bindings": {InputContractSHA256: "sha256:c9fc55b5b8d67849adb8a10b441b73f33a9eb1b6b6afeee7ec698874e1f9cd50", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-bindings.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f61039fafcdb2ced7655c1be0bd9b46d689878f27aebdf12975c8e9f32cbd14e", FlagChoices: map[string][]string{}}, - "requirement-browser-server": {InputContractSHA256: "sha256:07bd092a6776545126490f4b35184f2b052cc12af28beda5fc7658cd0b7c7acb", InputSchemaSummary: []string{"workspace mode: schemaVersion=2", "workspace mode: workspaceId", "workspace mode: context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter", "workspace mode: diffInput=proofkit.requirement-semantic-diff-input schemaVersion=2 (optional)", "workspace mode: graphInput=proofkit.requirement-traceability-graph-input schemaVersion=2 (optional)", "--session-mode values: browse|one-shot-question", "one-shot-question requires --view workspace --serve --open", "--session-timeout-seconds is 1..7200 and requires one-shot-question", "source|proof|coverage|spec-tree modes retain their owner input contracts", "root-shape-only definition proofkit.requirement-browser-server.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:51543cb699ab4b64f536307976c49ce21944ee31befe3ab2994906d5b51aaf91", FlagChoices: map[string][]string{}}, - "requirement-context-compose": {InputContractSHA256: "sha256:2eb447e8fb4163c9c9a5bad9fe62daa0c00184e37b97ce960b0bd0c9e9c91a9a", InputSchemaSummary: []string{"schemaVersion=1", "catalogId", "specTree.path", "requirementSources[] (non-empty)", "requirementSources[].nodeId", "requirementSources[].path", "expectedSourceDigest (optional sha256 ref)", "proofBinding.path (optional)", "coverage.path (optional)", "exact catalog paths only; no discovery", "root-shape-only definition proofkit.requirement-context-compose.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:1458969fbb3e6fec27f89a7d46192fbcec3fae371ba66fcf735fccd247c4d289", FlagChoices: map[string][]string{}}, - "requirement-context-slice": {InputContractSHA256: "sha256:8894f3dcca52c1b2eb75cee1f5d005a00bd0d41cd62c5759ec0ac69f431d2fdc", InputSchemaSummary: []string{"schemaVersion=1", "sliceId", "context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter", "query.profile=routing|specification|proof|coverage|review", "query.nodeIds[]|requirementIds[]|ownerIds[]|lifecycleStates[]", "query.maxDepth=0..512", "query.maxNodes=1..4096", "query.maxRequirements=1..16384", "root-shape-only definition proofkit.requirement-context-slice.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:ce06b9b2ca9af894f6f452da5bc872c821721e723c627b26264a6dcecee89819", FlagChoices: map[string][]string{}}, + "requirement-browser-server": {InputContractSHA256: "sha256:aee38d90bd8a58137200da3ac277c6d5ec9171c75b499bafd48ad31bb793b934", InputSchemaSummary: []string{"workspace mode: schemaVersion=2", "workspace mode: workspaceId", "workspace mode: context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter", "workspace mode: diffInput=proofkit.requirement-semantic-diff-input schemaVersion=2 (optional)", "workspace mode: graphInput=proofkit.requirement-traceability-graph-input schemaVersion=2 (optional)", "--session-mode values: browse|one-shot-question", "one-shot-question requires --view workspace --serve --open", "--session-timeout-seconds is 1..7200 and requires one-shot-question", "source|proof|coverage|spec-tree modes retain their owner input contracts", "root-shape-only definition proofkit.requirement-browser-server.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:0c95a66fdb2543c8839cff5e71ed5f027f99fb802dd32699a4e7d1eb53c99cdf", FlagChoices: map[string][]string{}}, + "requirement-context-compose": {InputContractSHA256: "sha256:847743017c9af0cf02f2403082bb1a45e6e18670d9ffecdc568e92ec62240927", InputSchemaSummary: []string{"schemaVersion=1", "catalogId", "specTree.path", "requirementSources[] (non-empty)", "requirementSources[].nodeId", "requirementSources[].path", "expectedSourceDigest (optional sha256 ref)", "proofBinding.path (optional)", "coverage.path (optional)", "exact catalog paths only; no discovery", "root-shape-only definition proofkit.requirement-context-compose.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:9e6bb924f291b56c8da5162d6d8584c2b90313fcec62a8e3b9f2e68e9fc123a5", FlagChoices: map[string][]string{}}, + "requirement-context-slice": {InputContractSHA256: "sha256:12576719455258fdaf1e9c2c8f982758e848bce1886f43188f458cf01a3864f8", InputSchemaSummary: []string{"schemaVersion=1", "sliceId", "context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter", "query.profile=routing|specification|proof|coverage|review", "query.nodeIds[]|requirementIds[]|ownerIds[]|lifecycleStates[]", "query.maxDepth=0..512", "query.maxNodes=1..4096", "query.maxRequirements=1..16384", "root-shape-only definition proofkit.requirement-context-slice.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:bca73df6487d9253925d1ce4e209d16af75b1bbd46fc72e3f2982be3a2d9b42e", FlagChoices: map[string][]string{}}, "requirement-coverage-input-compose": {InputContractSHA256: "sha256:26e46a1438f5633a835f4afaef293bed5417b13f4cd4c0a752d02659fcd094c6", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-coverage-input-compose.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:4e246b49fd99038d4b1df9ce484ef2214c3129769df2aaf67b398367a451dd86", FlagChoices: map[string][]string{}}, "requirement-coverage-view": {InputContractSHA256: "sha256:afb8646a0beb81a27815c436423e0b273855217f5d78af28b334682bd8100211", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-coverage-view.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:0a353e48748887d7c2b4ab0dc68b48c0d4630ff42c954a490f94cfde05b38412", FlagChoices: map[string][]string{}}, "requirement-impact-input-compose": {InputContractSHA256: "sha256:dd90693ee688680315d371160d5b4de936ad7a1fb9c4b2725c66cead311f983e", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-impact-input-compose.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:3a31f2500447918a4f77ec0c9c385944db603ac602bb7122957ce72cc8fe2f8b", FlagChoices: map[string][]string{}}, "requirement-proof-resolver": {InputContractSHA256: "sha256:6b032c893c770f260d72976b1ead28455e2775988771eee85cb8065bcdb7a91d", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-proof-resolver.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:99cf48212bfbfc4667a4951c0b4f4480701bfe5715028b6b05db28ecc0b0ae50", FlagChoices: map[string][]string{}}, - "requirement-proof-source-set": {InputContractSHA256: "sha256:68df60452e7403b2051214bffe045372a90063ba9ef7cf8714cfc1ea5e16fa5b", InputSchemaSummary: []string{"canonicalEnvelope", "sourceSet", "sources", "root-shape-only definition proofkit.requirement-proof-source-set.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c74a57fd4ada0a06c0fafd31ea1aedb20035c67e8e7ef0fc413ae8bf086143b3", FlagChoices: map[string][]string{}}, + "requirement-proof-source-set": {InputContractSHA256: "sha256:3ffe27b751445e4c525513ef2953f33fda2db49b4c84f6b9e93efc67b645a814", InputSchemaSummary: []string{"canonicalEnvelope", "sourceSet", "sources", "root-shape-only definition proofkit.requirement-proof-source-set.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c74a57fd4ada0a06c0fafd31ea1aedb20035c67e8e7ef0fc413ae8bf086143b3", FlagChoices: map[string][]string{}}, "requirement-proof-view": {InputContractSHA256: "sha256:236b5c87d83c64f13143cab74f2e3e50898f09ff1ec07590d6b6cce51a90ba7d", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-proof-view.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:c31930829de0cf5fec96b92bb155f21e2836ff8e5ac7b29b6c1b514c9a33390e", FlagChoices: map[string][]string{}}, "requirement-semantic-diff": {InputContractSHA256: "sha256:c44263f7ddce2949506ccaeb73e32e78c16bad3250cc6575d355f5874d9fdaa4", InputSchemaSummary: []string{"schemaVersion=2", "diffId", "baseContext=proofkit.requirement-context schemaVersion=2", "currentContext=proofkit.requirement-context schemaVersion=2", "strict schemaVersion=1 adapter requires two v1 contexts", "query.requirementIds[] (optional)", "query.ownerIds[] (optional)", "query.maxChanges=1..8192 (optional)", "root-shape-only definition proofkit.requirement-semantic-diff.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f88bcb2815f7ee1d66ca5ec06d090dd7f2de4ce8430529621742474c0edf992b", FlagChoices: map[string][]string{}}, "requirement-source-admission": {InputContractSHA256: "sha256:748da7b4f6bba55877cfe51efcbd85fbdab2f2329a9d9668ea2905713c80c417", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-source-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:2f6a2db4163b1829bd4cdf27890477abd77697d70dfd591943d47b7be798a90e", FlagChoices: map[string][]string{}}, @@ -72,11 +72,11 @@ var generatedCommandContractMetadataByName = map[string]generatedCommandContract "requirement-traceability-graph": {InputContractSHA256: "sha256:c3dfdd8e25325f4e1170559a4d6ab905a82cca92e5040ea22232596b93d389f1", InputSchemaSummary: []string{"schemaVersion=2", "graphId", "context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter", "codeSources[].path+content (optional, bounded UTF-8)", "codeTopology.nodes[].abstractionLevel=repository|package|module|file|symbol|source_range", "codeTopology.nodes[].sourceDigest+currentnessState", "codeTopology.edges[].evidenceRefs+authorityClass+currentnessState", "codeTopology.nativeCoverage[].producerId+evidenceRef+authorityClass+currentnessState+state", "root-shape-only definition proofkit.requirement-traceability-graph.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:5f1c027b553fb9dd558cd296b22d26371a69cbf4c0aeac38ffd51eb1b8505122", FlagChoices: map[string][]string{}}, "scaffold-profile-plan": {InputContractSHA256: "sha256:bc2a9dc33664fc0555bb5c4b67c6c2caa451995f7bcb1b8add8ea8a8aabd88a6", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.scaffold-profile-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:3d5d6584ef88c14534333e62b677ecac73d5bae659edf71893faa7ab1068659c", FlagChoices: map[string][]string{}}, "scaffold-project-structure": {InputContractSHA256: "sha256:0db5eca08d353a8d314908a34d8293c947a9d208e280353d9784b489576ec55a", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.scaffold-project-structure.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:79950f8b779b00616be24b2d7e28021a83e9414881676ae86ab467940d36e6cb", FlagChoices: map[string][]string{}}, - "secret-scan": {InputContractSHA256: "sha256:cf88b1608360f7f0efe6a65e547f327c721d3befef2136471a14b16d015625ab", InputSchemaSummary: []string{"files", "nonClaims", "reportId", "schemaVersion", "root-shape-only definition proofkit.secret-scan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:b26eb2f693f2bc69f30d19f8e17ed2d3f35a5c3201bbff7e261b3eb671d48118", FlagChoices: map[string][]string{}}, + "secret-scan": {InputContractSHA256: "sha256:6778f48f02a24472ab9e8172ba67dc1f77b0bf4a5bb58dd318aa91008315008a", InputSchemaSummary: []string{"files", "nonClaims", "reportId", "schemaVersion", "root-shape-only definition proofkit.secret-scan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:b26eb2f693f2bc69f30d19f8e17ed2d3f35a5c3201bbff7e261b3eb671d48118", FlagChoices: map[string][]string{}}, "selective-gate-evidence": {InputContractSHA256: "sha256:345d1f2bf38ba2b1b18b64c2ed1298d3d25c8153a7b0504a86adfcbfd2e93077", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.selective-gate-evidence.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:2e51ee45b106004daea83fe39a2d812b72ca77cdd35a893ad81150d6d7952068", FlagChoices: map[string][]string{}}, - "selective-gate-obligation-decision-input": {InputContractSHA256: "sha256:b404d9aab9362490006bd21b1919b9664af37a8e7c96ce8833f7e6541ded71fc", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.selective-gate-obligation-decision-input.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f6a493fe11ea2d2ccfa9f9bea186ab1ccee02d0f15306cb297b567a638f385d2", FlagChoices: map[string][]string{}}, + "selective-gate-obligation-decision-input": {InputContractSHA256: "sha256:dca418fcfbbd7c8ef67db613f0abd5ce62cfdcbdf112ab6cac2a043d18d21e31", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.selective-gate-obligation-decision-input.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f6a493fe11ea2d2ccfa9f9bea186ab1ccee02d0f15306cb297b567a638f385d2", FlagChoices: map[string][]string{}}, "selective-gate-plan": {InputContractSHA256: "sha256:5293a5a4c7d8426cf637e6f8d252095ca0eb1714365bb89bec83307b778c678a", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.selective-gate-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:d7bffed853af5595af08b03859be01c283a3bdff1b3502d94ddc190889977647", FlagChoices: map[string][]string{}}, - "self-check": {InputContractSHA256: "sha256:9a959ace6f23c7ad731276ce5d0408b7e449de5ac03d8e73734ef803f726f718", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.self-check.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:3efb6efe482e19d3930fc2662060390565e61bb8b4259c87708915763d13818d", FlagChoices: map[string][]string{}}, + "self-check": {InputContractSHA256: "sha256:422fcafeaf6aab4ffab4ee2ea71b256e42fda543ae6be1fbcdbbb6e6308ef085", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.self-check.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:99cdd9583ca3641c7e1c30e829e35c7eef1b90bd997b791323e309f4431f441c", FlagChoices: map[string][]string{}}, "spec-overview-claims": {InputContractSHA256: "sha256:2490dcd34ba7485e13f8f33e8a288a0463c4c52cc6b0d82c57777466927e49a4", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.spec-overview-claims.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:554f3a7020e9820ccb90672629fd769c52b2f298f356040aa3b0a817666cbfbf", FlagChoices: map[string][]string{}}, "spec-proof-bundle-admission": {InputContractSHA256: "sha256:6b6c2875b6476e63a1911e7d6112d9999df2babbee969f84abc4c9e4b470c933", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.spec-proof-bundle-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:e9e0eb66cebca3b99fe5036fb2e7327a9284934ed76f58818d18094d0546fc52", FlagChoices: map[string][]string{}}, "stack-preset": {InputContractSHA256: "", InputSchemaSummary: []string(nil), OutputContractSHA256: "sha256:f495e9ade4e1e7af7a8f8b2059f7611cc016e6080363afccf76d8dfc2dbc6d2d", FlagChoices: map[string][]string{"--preset": []string{"agentic_runtime_repo", "generated_docs_contract_repo", "python_service", "python_typescript_service", "typescript_monorepo", "typescript_workspace"}}}, diff --git a/internal/app/command_coverage_source.go b/internal/app/command_coverage_source.go index 8724438..7150d1b 100644 --- a/internal/app/command_coverage_source.go +++ b/internal/app/command_coverage_source.go @@ -7,6 +7,8 @@ import ( "os" "path/filepath" "strconv" + + "github.com/research-engineering/agentic-proofkit/internal/kernel/gotestsource" ) const commandCoverageSourceOracleImport = "github.com/research-engineering/agentic-proofkit/internal/testsupport/commandcoverage" @@ -77,7 +79,7 @@ func goTestFunctionProblemWithMarker(filePath string, testName string, marker st if function.Body == nil || len(function.Body.List) == 0 { return "has no executable body" } - if hasTestSkip(function) { + if gotestsource.HasSkip(function) { return "contains t.Skip and cannot serve as an always-executable semantic oracle" } if !hasFailureCapableAssertion(function) { @@ -202,36 +204,3 @@ func testingTParamName(function *ast.FuncDecl) string { } return function.Type.Params.List[0].Names[0].Name } - -func hasTestSkip(function *ast.FuncDecl) bool { - paramName := testingTParamName(function) - if paramName == "" || function.Body == nil { - return false - } - found := false - ast.Inspect(function.Body, func(node ast.Node) bool { - if found { - return false - } - call, ok := node.(*ast.CallExpr) - if !ok { - return true - } - selector, ok := call.Fun.(*ast.SelectorExpr) - if !ok { - return true - } - receiver, ok := selector.X.(*ast.Ident) - if !ok || receiver.Name != paramName { - return true - } - switch selector.Sel.Name { - case "Skip", "Skipf", "SkipNow": - found = true - return false - default: - return true - } - }) - return found -} diff --git a/internal/command/publicapi/public_api_test.go b/internal/command/publicapi/public_api_test.go index 65ec67a..3d07f4b 100644 --- a/internal/command/publicapi/public_api_test.go +++ b/internal/command/publicapi/public_api_test.go @@ -355,7 +355,7 @@ func TestScanCacheBindsBytesToFirstCanonicalIdentityAcrossSymlinkRetarget(t *tes } linkPath := filepath.Join(repoRoot, "entry.ts") if err := os.Symlink("a.ts", linkPath); err != nil { - t.Skipf("symlink unavailable: %v", err) + t.Fatalf("symlink unavailable: %v", err) } scan := newScanCache(repoRoot, maxAggregateScanBytes) if scan.initErr != nil { @@ -462,7 +462,7 @@ func TestCanonicalSourceSnapshotRejectsChangedCrossAliasAdmission(t *testing.T) } for _, alias := range []string{"one.ts", "two.ts", "three.ts"} { if err := os.Symlink("real.ts", filepath.Join(packageRoot, alias)); err != nil { - t.Skipf("symlink unavailable: %v", err) + t.Fatalf("symlink unavailable: %v", err) } } scan := newScanCache(repoRoot, maxAggregateScanBytes) diff --git a/internal/command/readinesscloseout/readinesscloseout.go b/internal/command/readinesscloseout/readinesscloseout.go index e7edf69..5a2561f 100644 --- a/internal/command/readinesscloseout/readinesscloseout.go +++ b/internal/command/readinesscloseout/readinesscloseout.go @@ -873,7 +873,11 @@ func uniqueTextArray(raw any, context string) ([]string, error) { func textValue(raw any, context string, allowEmpty bool, allowSecretLike bool) (string, error) { value, ok := raw.(string) - if !ok || (!allowEmpty && value == "") { + if !ok { + return "", fmt.Errorf("%s must be non-empty text", context) + } + value = strings.TrimSpace(value) + if !allowEmpty && value == "" { return "", fmt.Errorf("%s must be non-empty text", context) } if strings.ContainsRune(value, '\x00') { diff --git a/internal/command/readinesscloseout/readinesscloseout_test.go b/internal/command/readinesscloseout/readinesscloseout_test.go index 368955a..baeb259 100644 --- a/internal/command/readinesscloseout/readinesscloseout_test.go +++ b/internal/command/readinesscloseout/readinesscloseout_test.go @@ -106,6 +106,16 @@ func TestBuildRejectsCallerControlledReportKind(t *testing.T) { } } +func TestBuildRejectsWhitespaceOnlyReason(t *testing.T) { + input := minimalCloseoutInput(closedFrontierMarkdown()) + input["inputDefinitions"].([]any)[0].(map[string]any)["reason"] = " \t " + + _, status, err := Build(input) + if status != 1 || err == nil || !strings.Contains(err.Error(), "must be non-empty text") { + t.Fatalf("Build() status=%d error=%v, want whitespace-only reason rejection", status, err) + } +} + func TestBuildRejectsPassedClassificationForBlockedOwnerRow(t *testing.T) { markdown := strings.Join([]string{ "### Production Readiness Roadmap", diff --git a/internal/command/repoprofileadmission/repo_profile_admission.go b/internal/command/repoprofileadmission/repo_profile_admission.go index f44dfd7..f14fb88 100644 --- a/internal/command/repoprofileadmission/repo_profile_admission.go +++ b/internal/command/repoprofileadmission/repo_profile_admission.go @@ -11,7 +11,7 @@ import ( "github.com/research-engineering/agentic-proofkit/internal/kernel/report" ) -var boundaryNonClaims = []any{ +var boundaryNonClaims = []string{ "Repo-profile structural admission does not read repository state.", "Repo-profile structural admission does not execute native witnesses.", "Repo-profile structural admission does not prove command pass evidence.", @@ -142,6 +142,7 @@ type admissionResult struct { EnvironmentClassCount int GeneratedArtifactCount int Failures []string + NonClaims []string } func Build(raw any) (report.Record, int, error) { @@ -195,13 +196,14 @@ func verify(raw any) (admissionResult, error) { for _, entry := range globEntries(input.Profile) { for _, glob := range entry.Globs { - if err := pathpattern.Validate(glob, entry.Label+" glob"); err != nil { + pattern, err := pathpattern.Compile(glob, entry.Label+" glob") + if err != nil { failures = append(failures, err.Error()) continue } matched := false for _, tracked := range input.Facts.TrackedFiles { - if pathpattern.Match(glob, tracked) { + if pattern.MatchAdmitted(tracked) { matched = true break } @@ -235,6 +237,10 @@ func verify(raw any) (admissionResult, error) { return admissionResult{}, err } failures = append(failures, commandFailures...) + nonClaims, err := admit.MergeNonClaims(boundaryNonClaims, input.Profile.NonClaims, "repo profile report") + if err != nil { + return admissionResult{}, err + } return admissionResult{ CommandMatcherCount: len(input.Profile.CommandMatchers), @@ -242,6 +248,7 @@ func verify(raw any) (admissionResult, error) { EnvironmentClassCount: len(input.Profile.Proofs.EnvironmentClasses), GeneratedArtifactCount: len(input.Profile.Documents.GeneratedArtifacts), Failures: sortedUniqueFailures(failures), + NonClaims: nonClaims, }, nil } @@ -281,7 +288,7 @@ func buildReport(result admissionResult) report.Record { }, Diagnostics: []report.Diagnostic{}, RuleResults: rules, - NonClaims: boundaryNonClaims, + NonClaims: admit.StringSliceToAny(result.NonClaims), } } diff --git a/internal/command/repoprofileadmission/repo_profile_admission_test.go b/internal/command/repoprofileadmission/repo_profile_admission_test.go index 03678c5..1cea6ef 100644 --- a/internal/command/repoprofileadmission/repo_profile_admission_test.go +++ b/internal/command/repoprofileadmission/repo_profile_admission_test.go @@ -17,6 +17,17 @@ func TestBuildAdmitsValidRepoProfileAndRejectsRootPackageMismatch(t *testing.T) encoded, _ := json.Marshal(record) t.Fatalf("Build() exit=%d record=%s, want passed", exitCode, string(encoded)) } + if !strings.Contains(strings.Join(admitNonClaims(record.NonClaims), "\n"), "Repo profile test input does not read repository state.") { + t.Fatalf("Build() dropped caller nonClaims: %#v", record.NonClaims) + } + record.NonClaims[0] = "mutated by caller" + second, secondExit, secondErr := Build(validRepoProfileInput()) + if secondErr != nil || secondExit != 0 { + t.Fatalf("second Build() exit=%d error=%v", secondExit, secondErr) + } + if strings.Contains(strings.Join(admitNonClaims(second.NonClaims), "\n"), "mutated by caller") { + t.Fatalf("Build() leaked prior report mutation: %#v", second.NonClaims) + } input := validRepoProfileInput() input["facts"].(map[string]any)["rootPackageName"] = "other-root" @@ -30,6 +41,17 @@ func TestBuildAdmitsValidRepoProfileAndRejectsRootPackageMismatch(t *testing.T) assertRecordContains(t, record.JSONValue(), "rootPackageName must match") } +func admitNonClaims(values []any) []string { + out := make([]string, 0, len(values)) + for _, value := range values { + text, ok := value.(string) + if ok { + out = append(out, text) + } + } + return out +} + func TestBuildAdmitsOptionalInputSchemaVersionOne(t *testing.T) { input := validRepoProfileInput() input["schemaVersion"] = json.Number("1") diff --git a/internal/command/requirementbrowser/http_handler.go b/internal/command/requirementbrowser/http_handler.go index 86d17ac..a131c87 100644 --- a/internal/command/requirementbrowser/http_handler.go +++ b/internal/command/requirementbrowser/http_handler.go @@ -5,6 +5,7 @@ import ( "crypto/subtle" "encoding/base64" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -27,9 +28,16 @@ const ( maxHandoffQuestionBytes = 4 << 10 maxHandoffContextBytes = 1 << 20 maxHandoffPacketBytes = 2 << 20 + maxWorkspaceRequests = 4 +) + +var ( + errUnauthorizedAPIRequest = errors.New("unauthorized browser API request") + errStaleAPIRequest = errors.New("browser API request snapshot is stale") ) func browserHandler(view string, rendered renderedView, expectedAuthority, capability string, oneShot bool, terminal *terminalArbiter) http.Handler { + workspaceRequests := make(chan struct{}, maxWorkspaceRequests) return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { if request.Host != expectedAuthority { forbidden(response, request.Method) @@ -67,18 +75,30 @@ func browserHandler(view string, rendered renderedView, expectedAuthority, capab methodNotAllowed(response, method, "POST") return } + if !admitWorkspaceRequest(response, workspaceRequests) { + return + } + defer releaseWorkspaceRequest(workspaceRequests) serveWorkspaceQuery(response, request, expectedOrigin, capability, rendered.workspace) case "/api/v1/requirements": if method != http.MethodPost { methodNotAllowed(response, method, "POST") return } + if !admitWorkspaceRequest(response, workspaceRequests) { + return + } + defer releaseWorkspaceRequest(workspaceRequests) serveWorkspaceRequirements(response, request, expectedOrigin, capability, rendered.workspace) case "/api/v1/diff", "/api/v1/graph": if method != http.MethodPost { methodNotAllowed(response, method, "POST") return } + if !admitWorkspaceRequest(response, workspaceRequests) { + return + } + defer releaseWorkspaceRequest(workspaceRequests) serveWorkspaceProjection(response, request, expectedOrigin, capability, rendered.workspace) case "/api/v1/cancel": if method != http.MethodPost { @@ -91,6 +111,10 @@ func browserHandler(view string, rendered renderedView, expectedAuthority, capab methodNotAllowed(response, method, "POST") return } + if !admitWorkspaceRequest(response, workspaceRequests) { + return + } + defer releaseWorkspaceRequest(workspaceRequests) serveHandoff(response, request, expectedOrigin, capability, rendered.workspace, oneShot, terminal) default: response.WriteHeader(http.StatusNotFound) @@ -99,6 +123,20 @@ func browserHandler(view string, rendered renderedView, expectedAuthority, capab }) } +func admitWorkspaceRequest(response http.ResponseWriter, requests chan struct{}) bool { + select { + case requests <- struct{}{}: + return true + default: + response.WriteHeader(http.StatusTooManyRequests) + return false + } +} + +func releaseWorkspaceRequest(requests chan struct{}) { + <-requests +} + func serveWorkspaceRequirements(response http.ResponseWriter, request *http.Request, expectedOrigin, capability string, session *workspaceSession) { record, err := admitAPIRequest(request, expectedOrigin, capability, session, []string{"query", "requestId", "snapshotId"}) if err != nil { @@ -152,12 +190,7 @@ func serveWorkspaceQuery(response http.ResponseWriter, request *http.Request, ex return } queryID := digest.SHA256TextRef(string(queryBytes)) - slice, err := requirementcontext.Slice(map[string]any{ - "context": session.ContextValue, - "query": query, - "schemaVersion": json.Number("1"), - "sliceId": "browser.query:" + requestID, - }) + slice, err := requirementcontext.SliceSnapshot(session.Snapshot, query, "browser.query:"+requestID) if err != nil { writeAPIError(response, request.Method, err) return @@ -368,7 +401,7 @@ func serveCancel(response http.ResponseWriter, request *http.Request, expectedOr func admitAPIRequest(request *http.Request, expectedOrigin, capability string, session *workspaceSession, keys []string) (map[string]any, error) { if session == nil || request.Header.Get("origin") != expectedOrigin || request.Header.Get("content-type") != "application/json" || !validCapability(request, capability) { - return nil, fmt.Errorf("unauthorized browser API request") + return nil, errUnauthorizedAPIRequest } if request.ContentLength > maxHandoffRequestBytes { return nil, fmt.Errorf("browser API request exceeds byte limit") @@ -385,16 +418,16 @@ func admitAPIRequest(request *http.Request, expectedOrigin, capability string, s return nil, err } if record["snapshotId"] != session.SnapshotID { - return nil, fmt.Errorf("browser API request snapshot is stale") + return nil, errStaleAPIRequest } return record, nil } func writeAPIError(response http.ResponseWriter, method string, err error) { status := http.StatusBadRequest - if strings.Contains(err.Error(), "unauthorized") { + if errors.Is(err, errUnauthorizedAPIRequest) { status = http.StatusForbidden - } else if strings.Contains(err.Error(), "stale") { + } else if errors.Is(err, errStaleAPIRequest) { status = http.StatusConflict } response.WriteHeader(status) @@ -564,12 +597,11 @@ func buildHandoffPacket(request *http.Request, session workspaceSession) (map[st for index, requirementID := range orderedRequirementIDs { selectedRequirementIDs[index] = requirementID } - slice, err := requirementcontext.Slice(map[string]any{ - "context": session.ContextValue, - "query": map[string]any{"maxNodes": json.Number("4096"), "maxRequirements": json.Number("16384"), "profile": "review", "requirementIds": selectedRequirementIDs}, - "schemaVersion": json.Number("1"), - "sliceId": "browser.handoff.context", - }) + slice, err := requirementcontext.SliceSnapshot( + session.Snapshot, + map[string]any{"maxNodes": json.Number("4096"), "maxRequirements": json.Number("16384"), "profile": "review", "requirementIds": selectedRequirementIDs}, + "browser.handoff.context", + ) if err != nil { return nil, err } diff --git a/internal/command/requirementbrowser/http_handler_test.go b/internal/command/requirementbrowser/http_handler_test.go new file mode 100644 index 0000000..93aea14 --- /dev/null +++ b/internal/command/requirementbrowser/http_handler_test.go @@ -0,0 +1,50 @@ +package requirementbrowser + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" +) + +func TestWriteAPIErrorUsesTypedClassification(t *testing.T) { + tests := []struct { + name string + err error + status int + }{ + {name: "ordinary validation", err: errors.New("unsupported field unauthorized and stale"), status: http.StatusBadRequest}, + {name: "unauthorized", err: errUnauthorizedAPIRequest, status: http.StatusForbidden}, + {name: "stale", err: errStaleAPIRequest, status: http.StatusConflict}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + response := httptest.NewRecorder() + writeAPIError(response, http.MethodPost, test.err) + if response.Code != test.status { + t.Fatalf("status=%d, want %d", response.Code, test.status) + } + }) + } +} + +func TestWorkspaceRequestAdmissionIsBounded(t *testing.T) { + requests := make(chan struct{}, 1) + first := httptest.NewRecorder() + if !admitWorkspaceRequest(first, requests) { + t.Fatal("first request must be admitted") + } + second := httptest.NewRecorder() + if admitWorkspaceRequest(second, requests) { + t.Fatal("request beyond the concurrency bound must be rejected") + } + if second.Code != http.StatusTooManyRequests { + t.Fatalf("status=%d want %d", second.Code, http.StatusTooManyRequests) + } + releaseWorkspaceRequest(requests) + third := httptest.NewRecorder() + if !admitWorkspaceRequest(third, requests) { + t.Fatal("released capacity must admit the next request") + } + releaseWorkspaceRequest(requests) +} diff --git a/internal/command/requirementbrowser/server.go b/internal/command/requirementbrowser/server.go index b0a70ad..57c9c18 100644 --- a/internal/command/requirementbrowser/server.go +++ b/internal/command/requirementbrowser/server.go @@ -142,7 +142,10 @@ func StartServer(raw any, options Options) (ServerHandle, error) { if shutdownErr == nil { return nil } - return errors.Join(shutdownErr, server.Close()) + if closeErr := server.Close(); closeErr != nil { + return errors.Join(shutdownErr, closeErr) + } + return nil }, done: done, terminal: terminal, diff --git a/internal/command/requirementbrowser/server_test.go b/internal/command/requirementbrowser/server_test.go index 00ff606..7ac6d13 100644 --- a/internal/command/requirementbrowser/server_test.go +++ b/internal/command/requirementbrowser/server_test.go @@ -390,7 +390,9 @@ func TestServerCloseForcesTerminationAfterGracefulDeadline(t *testing.T) { } shutdownCtx, cancel := context.WithTimeout(t.Context(), 20*time.Millisecond) defer cancel() - _ = handle.Close(shutdownCtx) + if err := handle.Close(shutdownCtx); err != nil { + t.Fatalf("forced close after graceful deadline returned error: %v", err) + } select { case <-handle.Done(): case <-time.After(time.Second): diff --git a/internal/command/requirementbrowser/workspace.go b/internal/command/requirementbrowser/workspace.go index b2bf915..578a346 100644 --- a/internal/command/requirementbrowser/workspace.go +++ b/internal/command/requirementbrowser/workspace.go @@ -25,11 +25,11 @@ type workspaceAnchor struct { type workspaceSession struct { Anchors map[string]workspaceAnchor - ContextValue map[string]any Diff map[string]any Graph map[string]any Manifest map[string]any Requirements []any + Snapshot requirementcontext.Snapshot SnapshotID string } @@ -99,7 +99,7 @@ func buildWorkspace(raw any) (workspaceSession, string, error) { "snapshotId": snapshot.SnapshotID, "workspaceId": workspaceID, } - return workspaceSession{Anchors: anchors, ContextValue: requirementcontext.SnapshotValue(snapshot), Diff: diff, Graph: graph, Manifest: manifest, Requirements: requirements, SnapshotID: snapshot.SnapshotID}, workspaceHTML(workspaceID), nil + return workspaceSession{Anchors: anchors, Diff: diff, Graph: graph, Manifest: manifest, Requirements: requirements, Snapshot: snapshot, SnapshotID: snapshot.SnapshotID}, workspaceHTML(workspaceID), nil } func admitWorkspaceInputVersion(record map[string]any) error { diff --git a/internal/command/requirementcontext/requirementcontext_test.go b/internal/command/requirementcontext/requirementcontext_test.go index 625b1fd..199b255 100644 --- a/internal/command/requirementcontext/requirementcontext_test.go +++ b/internal/command/requirementcontext/requirementcontext_test.go @@ -41,6 +41,16 @@ func TestComposeAndSliceRoundTrip(t *testing.T) { if err != nil { t.Fatalf("Slice() error = %v", err) } + direct, err := SliceSnapshot(snapshot, map[string]any{ + "profile": "specification", + "nodeIds": []any{"spec.root"}, + }, "consumer.context.slice") + if err != nil { + t.Fatalf("SliceSnapshot() error = %v", err) + } + if !sameStableJSON(t, output, direct) { + t.Fatal("SliceSnapshot() drifted from public Slice() projection") + } if output["state"] != "selected" || output["snapshotId"] != snapshot.SnapshotID { t.Fatalf("unexpected slice output: %#v", output) } @@ -55,6 +65,19 @@ func TestComposeAndSliceRoundTrip(t *testing.T) { } } +func sameStableJSON(t *testing.T, left, right any) bool { + t.Helper() + leftJSON, err := stablejson.Marshal(left) + if err != nil { + t.Fatal(err) + } + rightJSON, err := stablejson.Marshal(right) + if err != nil { + t.Fatal(err) + } + return bytes.Equal(leftJSON, rightJSON) +} + func TestV1DigestCoverageAdapters(t *testing.T) { root := fixtureRepository(t) for _, test := range []struct { diff --git a/internal/command/requirementcontext/slice.go b/internal/command/requirementcontext/slice.go index 9f1ffcf..9324c8e 100644 --- a/internal/command/requirementcontext/slice.go +++ b/internal/command/requirementcontext/slice.go @@ -44,15 +44,19 @@ func Slice(raw any) (map[string]any, error) { if !admit.JSONNumberEquals(record["schemaVersion"], 1) { return nil, fmt.Errorf("requirement context slice schemaVersion must be 1") } - sliceID, err := admit.RuleID(record["sliceId"], "requirement context sliceId") + snapshot, err := AdmitSnapshot(record["context"]) if err != nil { return nil, err } - snapshot, err := AdmitSnapshot(record["context"]) + return SliceSnapshot(snapshot, record["query"], record["sliceId"]) +} + +func SliceSnapshot(snapshot Snapshot, rawQuery any, rawSliceID any) (map[string]any, error) { + sliceID, err := admit.RuleID(rawSliceID, "requirement context sliceId") if err != nil { return nil, err } - query, err := admitSliceQuery(record["query"]) + query, err := admitSliceQuery(rawQuery) if err != nil { return nil, err } diff --git a/internal/command/stackpreset/preset_ids_generated.go b/internal/command/stackpreset/preset_ids_generated.go index a9270de..5d7d057 100644 --- a/internal/command/stackpreset/preset_ids_generated.go +++ b/internal/command/stackpreset/preset_ids_generated.go @@ -1,6 +1,6 @@ // Code generated by internal/tools/commandcontractgen; DO NOT EDIT. package stackpreset -const presetContractSourceSHA256 = "90025ee656c5d90b1909423776121f12ce28b74b52d0eae2c34dd1bc55d2c6e9" +const presetContractSourceSHA256 = "611956ab74fd69cdede8fd37947c6c7ed05f3bd95ad6cc2f5249cbddf777d7d3" var presetIDs = []string{"agentic_runtime_repo", "generated_docs_contract_repo", "python_service", "python_typescript_service", "typescript_monorepo", "typescript_workspace"} diff --git a/internal/kernel/admission/json.go b/internal/kernel/admission/json.go index b2d0ab0..79491f4 100644 --- a/internal/kernel/admission/json.go +++ b/internal/kernel/admission/json.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "unicode" + "unicode/utf8" ) const maxJSONNestingDepth = 512 @@ -16,6 +17,9 @@ func DecodeJSON(reader io.Reader, maxBytes int64) (any, error) { if err != nil { return nil, err } + if !utf8.Valid(source) { + return nil, errors.New("invalid JSON input: source must be valid UTF-8") + } if err := assertUniqueObjectKeys(source); err != nil { return nil, err } @@ -102,7 +106,7 @@ func (scanner *jsonKeyScanner) parseValue() error { case '[': return scanner.parseArray() case '"': - _, err := scanner.parseString() + _, err := scanner.parseStringToken() return err case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': scanner.parseNumberLike() @@ -185,6 +189,18 @@ func (scanner *jsonKeyScanner) parseArray() error { } func (scanner *jsonKeyScanner) parseString() (string, error) { + token, err := scanner.parseStringToken() + if err != nil { + return "", err + } + var value string + if err := json.Unmarshal(token, &value); err != nil { + return "", fmt.Errorf("invalid JSON input: %w", err) + } + return value, nil +} + +func (scanner *jsonKeyScanner) parseStringToken() ([]byte, error) { start := scanner.index scanner.index++ for scanner.index < len(scanner.source) { @@ -193,16 +209,71 @@ func (scanner *jsonKeyScanner) parseString() (string, error) { scanner.index += 2 case '"': scanner.index++ - var value string - if err := json.Unmarshal(scanner.source[start:scanner.index], &value); err != nil { - return "", fmt.Errorf("invalid JSON input: %w", err) + token := scanner.source[start:scanner.index] + if err := validateJSONStringUnicodeEscapes(token); err != nil { + return nil, err + } + if !json.Valid(token) { + return nil, errors.New("invalid JSON input: invalid string token") } - return value, nil + return token, nil default: scanner.index++ } } - return "", errors.New("invalid JSON input: unterminated string") + return nil, errors.New("invalid JSON input: unterminated string") +} + +func validateJSONStringUnicodeEscapes(token []byte) error { + for index := 1; index < len(token)-1; index++ { + if token[index] != '\\' { + continue + } + index++ + if index >= len(token)-1 || token[index] != 'u' { + continue + } + codePoint, ok := decodeHexQuad(token, index+1) + if !ok { + continue + } + index += 4 + switch { + case codePoint >= 0xd800 && codePoint <= 0xdbff: + if index+6 >= len(token) || token[index+1] != '\\' || token[index+2] != 'u' { + return errors.New("invalid JSON input: unpaired Unicode surrogate") + } + low, ok := decodeHexQuad(token, index+3) + if !ok || low < 0xdc00 || low > 0xdfff { + return errors.New("invalid JSON input: unpaired Unicode surrogate") + } + index += 6 + case codePoint >= 0xdc00 && codePoint <= 0xdfff: + return errors.New("invalid JSON input: unpaired Unicode surrogate") + } + } + return nil +} + +func decodeHexQuad(source []byte, start int) (uint16, bool) { + if start+4 > len(source) { + return 0, false + } + var value uint16 + for _, character := range source[start : start+4] { + value <<= 4 + switch { + case character >= '0' && character <= '9': + value += uint16(character - '0') + case character >= 'a' && character <= 'f': + value += uint16(character-'a') + 10 + case character >= 'A' && character <= 'F': + value += uint16(character-'A') + 10 + default: + return 0, false + } + } + return value, true } func (scanner *jsonKeyScanner) parseNumberLike() { diff --git a/internal/kernel/admission/json_test.go b/internal/kernel/admission/json_test.go index b6ee9ad..5a865e1 100644 --- a/internal/kernel/admission/json_test.go +++ b/internal/kernel/admission/json_test.go @@ -1,6 +1,7 @@ package admission import ( + "bytes" "encoding/json" "strings" "testing" @@ -83,6 +84,38 @@ func TestDecodeJSONRejectsResourceLimit(t *testing.T) { } } +func TestDecodeJSONRejectsLossyUnicodeInputs(t *testing.T) { + tests := []struct { + name string + input []byte + }{ + {name: "invalid UTF-8 ff", input: []byte{'{', '"', 'v', '"', ':', '"', 0xff, '"', '}'}}, + {name: "invalid UTF-8 fe", input: []byte{'{', '"', 'v', '"', ':', '"', 0xfe, '"', '}'}}, + {name: "unpaired high surrogate", input: []byte(`{"v":"\ud800"}`)}, + {name: "unpaired low surrogate", input: []byte(`{"v":"\udc00"}`)}, + {name: "high surrogate followed by non-low surrogate", input: []byte(`{"v":"\ud800\u0041"}`)}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if _, err := DecodeJSON(bytes.NewReader(test.input), 1024); err == nil { + t.Fatal("DecodeJSON accepted a lossy Unicode input") + } + }) + } +} + +func TestDecodeJSONAcceptsUnicodeScalarValues(t *testing.T) { + for _, input := range []string{ + `{"literal":"` + string('\ufffd') + `"}`, + `{"escaped":"\ufffd"}`, + `{"pair":"\ud83d\ude80"}`, + } { + if _, err := DecodeJSON(strings.NewReader(input), 1024); err != nil { + t.Fatalf("DecodeJSON(%q) error=%v", input, err) + } + } +} + func TestDecodeJSONAcceptsNestedObjects(t *testing.T) { value, err := DecodeJSON(strings.NewReader(`{"items":[{"a":1},{"a":2}],"ok":true}`), 1024) if err != nil { diff --git a/internal/kernel/admit/fields.go b/internal/kernel/admit/fields.go index 5f1128f..3dd04c4 100644 --- a/internal/kernel/admit/fields.go +++ b/internal/kernel/admit/fields.go @@ -11,6 +11,7 @@ import ( var ( ruleIDPattern = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*(?:[._:-][A-Za-z0-9_]+)*$`) + ruleIDSeparatorPattern = regexp.MustCompile(`[._:-]`) timestampLikePattern = regexp.MustCompile(`\d{4}-\d{2}-\d{2}(?:T\d{2}:?\d{2}:?\d{2}(?:\.\d+)?Z?)?|\d{8}(?:T?\d{6}Z?)?`) isoDateComponentPattern = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}(?:T\d{2}:?\d{2}:?\d{2}(?:\.\d+)?Z?)?$`) compactDateComponentRegexp = regexp.MustCompile(`^\d{8}(?:T?\d{6}Z?)?$`) @@ -96,7 +97,7 @@ func RuleID(raw any, context string) (string, error) { if timestampLikePattern.MatchString(value) { return "", fmt.Errorf("%s must not contain timestamp-like identity components", context) } - for _, component := range regexp.MustCompile(`[._:-]`).Split(value, -1) { + for _, component := range ruleIDSeparatorPattern.Split(value, -1) { if isoDateComponentPattern.MatchString(component) || compactDateComponentRegexp.MatchString(component) { return "", fmt.Errorf("%s must not contain timestamp-like identity components", context) } @@ -263,25 +264,34 @@ func TextArray(raw any, context string, allowEmpty bool) ([]string, error) { return result, nil } -func SortedText(values []string, context string, allowEmpty bool) ([]string, error) { +func NormalizeSortedText(values []string, context string, allowEmpty bool) ([]string, error) { if !allowEmpty && len(values) == 0 { return nil, fmt.Errorf("%s must be non-empty", context) } - sort.Strings(values) - for index := 1; index < len(values); index++ { - if values[index-1] == values[index] { - return nil, fmt.Errorf("%s must be sorted and unique", context) + normalized := append([]string{}, values...) + sort.Strings(normalized) + for index := 1; index < len(normalized); index++ { + if normalized[index-1] == normalized[index] { + return nil, fmt.Errorf("%s must be unique", context) } } - return values, nil + return normalized, nil } -func SortedTextArray(raw any, context string, allowEmpty bool) ([]string, error) { +func NormalizeSortedTextArray(raw any, context string, allowEmpty bool) ([]string, error) { values, err := TextArray(raw, context, allowEmpty) if err != nil { return nil, err } - return SortedText(values, context, allowEmpty) + return NormalizeSortedText(values, context, allowEmpty) +} + +func SortedText(values []string, context string, allowEmpty bool) ([]string, error) { + return NormalizeSortedText(values, context, allowEmpty) +} + +func SortedTextArray(raw any, context string, allowEmpty bool) ([]string, error) { + return NormalizeSortedTextArray(raw, context, allowEmpty) } func MergeNonClaims(required []string, caller []string, context string) ([]string, error) { diff --git a/internal/kernel/admit/fields_test.go b/internal/kernel/admit/fields_test.go index 8bcb9e4..2653b9a 100644 --- a/internal/kernel/admit/fields_test.go +++ b/internal/kernel/admit/fields_test.go @@ -144,20 +144,24 @@ func TestRedactStructuralTextPreservesLongStructureAndRedactsSensitiveTokens(t * } } -func TestSortedTextEnforcesUniquenessAndNonEmptyPolicy(t *testing.T) { +func TestNormalizeSortedTextEnforcesUniquenessWithoutAliasing(t *testing.T) { t.Parallel() - values, err := SortedText([]string{"b", "a"}, "refs", false) + input := []string{"b", "a"} + values, err := NormalizeSortedText(input, "refs", false) if err != nil { t.Fatalf("expected sortable unique refs: %v", err) } if strings.Join(values, ",") != "a,b" { t.Fatalf("expected sorted refs, got %q", strings.Join(values, ",")) } - if _, err := SortedText([]string{"a", "a"}, "refs", false); err == nil { + if strings.Join(input, ",") != "b,a" { + t.Fatalf("input mutated to %q", strings.Join(input, ",")) + } + if _, err := NormalizeSortedText([]string{"a", "a"}, "refs", false); err == nil { t.Fatal("expected duplicate refs rejection") } - if _, err := SortedText([]string{}, "refs", false); err == nil { + if _, err := NormalizeSortedText([]string{}, "refs", false); err == nil { t.Fatal("expected empty refs rejection") } } diff --git a/internal/kernel/compactproofcontract/compactproofcontract.go b/internal/kernel/compactproofcontract/compactproofcontract.go index baf57a9..8e5b617 100644 --- a/internal/kernel/compactproofcontract/compactproofcontract.go +++ b/internal/kernel/compactproofcontract/compactproofcontract.go @@ -443,15 +443,15 @@ func mutationFindingCount(state string) int { } func resolverSurfaces(surfaces []Surface, requirements []any) []any { + requirementIDsBySurface := make(map[string][]string, len(surfaces)) + for _, requirementValue := range requirements { + requirement := requirementValue.(map[string]any) + surfaceID := requirement["surfaceId"].(string) + requirementIDsBySurface[surfaceID] = append(requirementIDsBySurface[surfaceID], requirement["requirementId"].(string)) + } result := make([]any, 0, len(surfaces)) for _, surface := range surfaces { - requirementIDs := []string{} - for _, requirementValue := range requirements { - requirement := requirementValue.(map[string]any) - if requirement["surfaceId"] == surface.SurfaceID { - requirementIDs = append(requirementIDs, requirement["requirementId"].(string)) - } - } + requirementIDs := requirementIDsBySurface[surface.SurfaceID] sort.Strings(requirementIDs) result = append(result, map[string]any{ "requirementIds": stringSliceToAny(requirementIDs), diff --git a/internal/kernel/gotestsource/oracle.go b/internal/kernel/gotestsource/oracle.go new file mode 100644 index 0000000..f35f9fb --- /dev/null +++ b/internal/kernel/gotestsource/oracle.go @@ -0,0 +1,47 @@ +package gotestsource + +import "go/ast" + +func HasSkip(function *ast.FuncDecl) bool { + paramName := testingParameterName(function) + if paramName == "" || function.Body == nil { + return false + } + found := false + ast.Inspect(function.Body, func(node ast.Node) bool { + if found { + return false + } + call, ok := node.(*ast.CallExpr) + if !ok { + return true + } + selector, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + receiver, ok := selector.X.(*ast.Ident) + if !ok || receiver.Name != paramName { + return true + } + switch selector.Sel.Name { + case "Skip", "Skipf", "SkipNow": + found = true + return false + default: + return true + } + }) + return found +} + +func testingParameterName(function *ast.FuncDecl) string { + if function.Type.Params == nil || len(function.Type.Params.List) != 1 { + return "" + } + parameter := function.Type.Params.List[0] + if len(parameter.Names) != 1 { + return "" + } + return parameter.Names[0].Name +} diff --git a/internal/kernel/gotestsource/oracle_test.go b/internal/kernel/gotestsource/oracle_test.go new file mode 100644 index 0000000..a6f7b68 --- /dev/null +++ b/internal/kernel/gotestsource/oracle_test.go @@ -0,0 +1,34 @@ +package gotestsource + +import ( + "go/ast" + "go/parser" + "go/token" + "testing" +) + +func TestHasSkipDistinguishesTestingParameterCalls(t *testing.T) { + tests := []struct { + name string + body string + want bool + }{ + {name: "skip", body: `t.Skip("blocked")`, want: true}, + {name: "skipf", body: `t.Skipf("blocked: %s", "reason")`, want: true}, + {name: "helper skip", body: `helper.Skip()`, want: false}, + {name: "ordinary assertion", body: `t.Fatal("failed")`, want: false}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + source := "package fixture\nimport \"testing\"\nfunc TestWitness(t *testing.T) { " + test.body + " }\n" + file, err := parser.ParseFile(token.NewFileSet(), "fixture_test.go", source, 0) + if err != nil { + t.Fatal(err) + } + function := file.Decls[1].(*ast.FuncDecl) + if got := HasSkip(function); got != test.want { + t.Fatalf("HasSkip()=%v, want %v", got, test.want) + } + }) + } +} diff --git a/internal/kernel/stablejson/stablejson.go b/internal/kernel/stablejson/stablejson.go index 8e3bad3..ec4a2f7 100644 --- a/internal/kernel/stablejson/stablejson.go +++ b/internal/kernel/stablejson/stablejson.go @@ -6,6 +6,7 @@ import ( "fmt" "regexp" "sort" + "strconv" "strings" ) @@ -52,7 +53,7 @@ func writeValue(builder *strings.Builder, value any, depth int, layout Layout) e } builder.WriteString(typed.String()) case int: - builder.WriteString(fmt.Sprintf("%d", typed)) + builder.WriteString(strconv.Itoa(typed)) case []any: return writeArray(builder, typed, depth, layout) case map[string]any: @@ -146,6 +147,9 @@ func writeIndent(builder *strings.Builder, depth int) { } func quote(value string) string { + if isUnescapedASCII(value) { + return `"` + value + `"` + } var buffer bytes.Buffer encoder := json.NewEncoder(&buffer) encoder.SetEscapeHTML(false) @@ -154,3 +158,13 @@ func quote(value string) string { } return strings.TrimSuffix(buffer.String(), "\n") } + +func isUnescapedASCII(value string) bool { + for index := 0; index < len(value); index++ { + character := value[index] + if character < 0x20 || character >= 0x80 || character == '"' || character == '\\' { + return false + } + } + return true +} diff --git a/internal/kernel/stablejson/stablejson_test.go b/internal/kernel/stablejson/stablejson_test.go index f3c831b..c9f430a 100644 --- a/internal/kernel/stablejson/stablejson_test.go +++ b/internal/kernel/stablejson/stablejson_test.go @@ -1,8 +1,10 @@ package stablejson import ( + "bytes" "encoding/json" "fmt" + "strings" "testing" ) @@ -81,3 +83,31 @@ func TestMarshalAcceptsJSONNumberGrammar(t *testing.T) { }) } } + +func TestQuoteFastPathMatchesCanonicalEncoder(t *testing.T) { + for _, value := range []string{ + "", + "plain ASCII", + "&text", + "quote\"slash\\", + "line\nbreak", + "cafe\u0301", + "\u2028\u2029", + string([]byte{0xff}), + } { + var buffer strings.Builder + if err := writeValue(&buffer, value, 0, LayoutCompact); err != nil { + t.Fatalf("writeValue(%q): %v", value, err) + } + var reference bytes.Buffer + encoder := json.NewEncoder(&reference) + encoder.SetEscapeHTML(false) + if err := encoder.Encode(value); err != nil { + t.Fatalf("encode reference %q: %v", value, err) + } + want := strings.TrimSuffix(reference.String(), "\n") + if buffer.String() != want { + t.Fatalf("quote(%q)=%q want %q", value, buffer.String(), want) + } + } +} diff --git a/internal/tools/browserproofverify/main.go b/internal/tools/browserproofverify/main.go index 0f03fc4..0ea5c86 100644 --- a/internal/tools/browserproofverify/main.go +++ b/internal/tools/browserproofverify/main.go @@ -238,17 +238,17 @@ func verifyRecord(root string, raw any, expectedResolution proofInputResolution, } identityAssets = append(identityAssets, map[string]any{"path": path, "sha256": sha}) } - encodedIdentity, err := json.Marshal(map[string]any{ + encodedIdentity, err := stablejson.MarshalLayout(map[string]any{ "assets": identityAssets, "inputResolution": map[string]any{ "serverTarget": expectedResolution.ServerTarget, "writerPath": expectedResolution.WriterPath, }, - }) + }, stablejson.LayoutCompact) if err != nil { return err } - expectedInputDigest := digest.SHA256TextRef(string(encodedIdentity)) + expectedInputDigest := digest.SHA256TextRef(strings.TrimSuffix(string(encodedIdentity), "\n")) if record["inputDigest"] != expectedInputDigest { return fmt.Errorf("browser runtime proof inputDigest mismatch: got %s want %s", admit.RedactStructuralText(fmt.Sprint(record["inputDigest"])), expectedInputDigest) } diff --git a/internal/tools/browserproofverify/main_test.go b/internal/tools/browserproofverify/main_test.go index 1fe7449..d4ab75f 100644 --- a/internal/tools/browserproofverify/main_test.go +++ b/internal/tools/browserproofverify/main_test.go @@ -8,10 +8,12 @@ import ( "os" "path/filepath" "slices" + "strings" "testing" "github.com/research-engineering/agentic-proofkit/internal/kernel/admission" "github.com/research-engineering/agentic-proofkit/internal/kernel/digest" + "github.com/research-engineering/agentic-proofkit/internal/kernel/stablejson" ) func TestInputManifestClosesGoDependenciesAndWitnessPolicy(t *testing.T) { @@ -211,7 +213,7 @@ func fixtureRecord(t *testing.T, root string, resolution proofInputResolution) m assets = append(assets, map[string]any{"path": path, "sha256": hex.EncodeToString(sum[:])}) } inputResolution := map[string]any{"serverTarget": resolution.ServerTarget, "writerPath": resolution.WriterPath} - encoded, err := json.Marshal(map[string]any{"assets": assets, "inputResolution": inputResolution}) + encoded, err := stablejson.MarshalLayout(map[string]any{"assets": assets, "inputResolution": inputResolution}, stablejson.LayoutCompact) if err != nil { t.Fatal(err) } @@ -222,7 +224,7 @@ func fixtureRecord(t *testing.T, root string, resolution proofInputResolution) m return map[string]any{ "assets": assets, "command": map[string]any{"argv": []any{"node_modules/@playwright/test/cli.js", "test"}, "exitCode": json.Number("0"), "inputMode": "materialized_snapshot", "runner": "node"}, "engines": []any{map[string]any{"name": "chromium", "version": "1"}, map[string]any{"name": "firefox", "version": "1"}, map[string]any{"name": "webkit", "version": "1"}}, - "inputDigest": digest.SHA256TextRef(string(encoded)), "inputResolution": inputResolution, "nonClaims": nonClaims, + "inputDigest": digest.SHA256TextRef(strings.TrimSuffix(string(encoded), "\n")), "inputResolution": inputResolution, "nonClaims": nonClaims, "projects": []any{ map[string]any{"browserName": "chromium", "browserVersion": "1", "executedTestCount": json.Number("1"), "name": "chromium", "passedTestCount": json.Number("1"), "testIds": []any{"tests/browser/workspace.spec.mjs::runs"}}, map[string]any{"browserName": "firefox", "browserVersion": "1", "executedTestCount": json.Number("1"), "name": "firefox", "passedTestCount": json.Number("1"), "testIds": []any{"tests/browser/workspace.spec.mjs::runs"}}, diff --git a/internal/tools/coveragemetrics/main.go b/internal/tools/coveragemetrics/main.go index 85bd099..2004b2a 100644 --- a/internal/tools/coveragemetrics/main.go +++ b/internal/tools/coveragemetrics/main.go @@ -21,6 +21,7 @@ import ( "github.com/research-engineering/agentic-proofkit/internal/command/requirementsourceadmission" "github.com/research-engineering/agentic-proofkit/internal/command/testevidenceinventory" "github.com/research-engineering/agentic-proofkit/internal/kernel/admission" + "github.com/research-engineering/agentic-proofkit/internal/kernel/gotestsource" "github.com/research-engineering/agentic-proofkit/internal/tools/packageartifactrecord" ) @@ -399,7 +400,7 @@ func validateRequiredBindingWitnessSelectors(bindings bindingFile) error { } func validateBindingWitnessSelectorExecutabilityAtRoot(root string, bindings bindingFile) error { - activeWitnessFiles := map[string]bool{} + activeWitnessPackages := map[string]map[string]struct{}{} for _, binding := range bindings.Bindings { if len(binding.WitnessSelectors) == 0 { continue @@ -426,6 +427,9 @@ func validateBindingWitnessSelectorExecutabilityAtRoot(root string, bindings bin if !validGoTestFunction(function, testingAliases, dotImportedTesting) { return fmt.Errorf("binding %s selector %s is not a valid Go test function", binding.ScenarioID, selector.Selector) } + if gotestsource.HasSkip(function) { + return fmt.Errorf("binding %s selector %s contains t.Skip and cannot serve as an always-executable witness", binding.ScenarioID, selector.Selector) + } expectedCommand := fmt.Sprintf("go test %s -run '^%s$'", packagePath, selector.Selector) if selector.Command != expectedCommand { return fmt.Errorf("binding %s selector command=%q, want %q", binding.ScenarioID, selector.Command, expectedCommand) @@ -434,27 +438,31 @@ func validateBindingWitnessSelectorExecutabilityAtRoot(root string, bindings bin if !strings.HasSuffix(binding.WitnessPath, "_test.go") { return fmt.Errorf("binding %s witness %s must be an active _test.go file", binding.ScenarioID, binding.WitnessPath) } - active, checked := activeWitnessFiles[binding.WitnessPath] + activeFiles, checked := activeWitnessPackages[packagePath] if !checked { - active, err = activeGoTestFile(root, packagePath, binding.WitnessPath) + activeFiles, err = activeGoTestFiles(root, packagePath) if err != nil { return fmt.Errorf("discover binding witness %s: %w", binding.WitnessPath, err) } - activeWitnessFiles[binding.WitnessPath] = active + activeWitnessPackages[packagePath] = activeFiles + } + witnessAbsolute, err := filepath.Abs(filepath.Join(root, filepath.FromSlash(binding.WitnessPath))) + if err != nil { + return err } - if !active { + if _, active := activeFiles[filepath.Clean(witnessAbsolute)]; !active { return fmt.Errorf("binding %s witness %s is not active for the current Go build", binding.ScenarioID, binding.WitnessPath) } } return nil } -func activeGoTestFile(root, packagePath, witnessPath string) (bool, error) { +func activeGoTestFiles(root, packagePath string) (map[string]struct{}, error) { command := exec.Command("go", "list", "-json", packagePath) command.Dir = root output, err := command.CombinedOutput() if err != nil { - return false, fmt.Errorf("go list %s: %w: %s", packagePath, err, strings.TrimSpace(string(output))) + return nil, fmt.Errorf("go list %s: %w: %s", packagePath, err, strings.TrimSpace(string(output))) } var listed struct { Dir string @@ -462,22 +470,17 @@ func activeGoTestFile(root, packagePath, witnessPath string) (bool, error) { XTestGoFiles []string } if err := json.Unmarshal(output, &listed); err != nil { - return false, fmt.Errorf("decode go list %s: %w", packagePath, err) - } - witnessAbsolute, err := filepath.Abs(filepath.Join(root, filepath.FromSlash(witnessPath))) - if err != nil { - return false, err + return nil, fmt.Errorf("decode go list %s: %w", packagePath, err) } + activeFiles := map[string]struct{}{} for _, file := range append(listed.TestGoFiles, listed.XTestGoFiles...) { activeAbsolute, err := filepath.Abs(filepath.Join(listed.Dir, file)) if err != nil { - return false, err - } - if filepath.Clean(activeAbsolute) == filepath.Clean(witnessAbsolute) { - return true, nil + return nil, err } + activeFiles[filepath.Clean(activeAbsolute)] = struct{}{} } - return false, nil + return activeFiles, nil } func importedTestingNames(source *ast.File) (map[string]struct{}, bool) { diff --git a/internal/tools/coveragemetrics/main_test.go b/internal/tools/coveragemetrics/main_test.go index f7677ee..9bde679 100644 --- a/internal/tools/coveragemetrics/main_test.go +++ b/internal/tools/coveragemetrics/main_test.go @@ -587,6 +587,29 @@ func TestBindingWitnessSelectorsRejectInvalidGoTestSignature(t *testing.T) { } } +func TestBindingWitnessSelectorsRejectSkippingTest(t *testing.T) { + root := t.TempDir() + witnessPath := filepath.Join("internal", "sample", "sample_test.go") + if err := os.MkdirAll(filepath.Join(root, filepath.Dir(witnessPath)), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(root, "go.mod"), []byte("module example.com/sample\n\ngo 1.25.0\n"), 0o644); err != nil { + t.Fatal(err) + } + source := "package sample\n\nimport \"testing\"\n\nfunc TestSkipping(t *testing.T) { t.Skip(\"blocked\") }\n" + if err := os.WriteFile(filepath.Join(root, witnessPath), []byte(source), 0o644); err != nil { + t.Fatal(err) + } + bindings := bindingFile{Bindings: []bindingScenario{bindingSelectorFixture( + "scenario.skipping", witnessPath, "TestSkipping", + )}} + + err := validateBindingWitnessSelectorExecutabilityAtRoot(root, bindings) + if err == nil || !strings.Contains(err.Error(), "contains t.Skip") { + t.Fatalf("skipping witness error=%v", err) + } +} + func TestBindingWitnessSelectorsRejectNonTestAndBuildExcludedFiles(t *testing.T) { t.Run("non-test source", func(t *testing.T) { root := t.TempDir() diff --git a/internal/tools/mermaidcheck/main.go b/internal/tools/mermaidcheck/main.go index c63b0a9..71e4b88 100644 --- a/internal/tools/mermaidcheck/main.go +++ b/internal/tools/mermaidcheck/main.go @@ -2,12 +2,14 @@ package main import ( "bufio" + "errors" "fmt" "io" "os" "os/exec" "path/filepath" "regexp" + "sort" "strings" ) @@ -69,16 +71,41 @@ func run(args []string, stdout io.Writer) error { } func markdownFilesFromGit() ([]string, error) { - cmd := exec.Command("git", "ls-files", "*.md") + return markdownFilesFromGitAt(".") +} + +func markdownFilesFromGitAt(dir string) ([]string, error) { + cmd := exec.Command("git", "ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", "*.md") + cmd.Dir = dir out, err := cmd.Output() if err != nil { - return nil, fmt.Errorf("list tracked Markdown files: %w", err) + return nil, fmt.Errorf("list candidate Markdown files: %w", err) } - lines := strings.Split(strings.TrimSpace(string(out)), "\n") - if len(lines) == 1 && lines[0] == "" { - return nil, nil + + var files []string + seen := make(map[string]struct{}) + for _, path := range strings.Split(string(out), "\x00") { + if path == "" { + continue + } + info, err := os.Stat(filepath.Join(dir, path)) + if errors.Is(err, os.ErrNotExist) { + continue + } + if err != nil { + return nil, fmt.Errorf("inspect candidate Markdown file %s: %w", path, err) + } + if !info.Mode().IsRegular() { + continue + } + if _, ok := seen[path]; ok { + continue + } + seen[path] = struct{}{} + files = append(files, path) } - return lines, nil + sort.Strings(files) + return files, nil } func extractMermaidBlocks(path string) ([]diagramBlock, error) { diff --git a/internal/tools/mermaidcheck/main_test.go b/internal/tools/mermaidcheck/main_test.go index df41251..d0f0ae2 100644 --- a/internal/tools/mermaidcheck/main_test.go +++ b/internal/tools/mermaidcheck/main_test.go @@ -4,7 +4,9 @@ import ( "bytes" "errors" "os" + "os/exec" "path/filepath" + "slices" "strings" "testing" ) @@ -53,6 +55,31 @@ func TestMermaidCheckRejectsQuotedDottedEdgeLabel(t *testing.T) { } } +func TestMarkdownFilesFromGitUsesCandidateWorktree(t *testing.T) { + dir := t.TempDir() + runGitForTest(t, dir, "init", "--quiet") + + writeTestFile(t, dir, ".gitignore", "ignored.md\n") + writeTestFile(t, dir, "deleted.md", "# Deleted\n") + writeTestFile(t, dir, "kept.md", "# Kept\n") + runGitForTest(t, dir, "add", ".gitignore", "deleted.md", "kept.md") + + if err := os.Remove(filepath.Join(dir, "deleted.md")); err != nil { + t.Fatal(err) + } + writeTestFile(t, dir, "new.md", "# New\n") + writeTestFile(t, dir, "ignored.md", "# Ignored\n") + + got, err := markdownFilesFromGitAt(dir) + if err != nil { + t.Fatal(err) + } + want := []string{"kept.md", "new.md"} + if !slices.Equal(got, want) { + t.Fatalf("candidate Markdown files = %v, want %v", got, want) + } +} + func checkFileContent(path, content string) error { tmp, err := os.CreateTemp("", "proofkit-mermaid-*.md") if err != nil { @@ -111,3 +138,19 @@ func runForTest(files map[string]string, stdout *bytes.Buffer) error { } return run(names, stdout) } + +func runGitForTest(t *testing.T, dir string, args ...string) { + t.Helper() + cmd := exec.Command("git", args...) + cmd.Dir = dir + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out) + } +} + +func writeTestFile(t *testing.T, dir, path, content string) { + t.Helper() + if err := os.WriteFile(filepath.Join(dir, path), []byte(content), 0o644); err != nil { + t.Fatal(err) + } +} diff --git a/internal/tools/packageverify/main_test.go b/internal/tools/packageverify/main_test.go index 8856cf8..c3c53fb 100644 --- a/internal/tools/packageverify/main_test.go +++ b/internal/tools/packageverify/main_test.go @@ -776,7 +776,7 @@ func TestExactTarballOnboardingTrace(t *testing.T) { contract := mustReadBytes(t, filepath.Join("..", "..", "..", "proofkit", "cli-contract.v2.json")) target, err := releaseplatform.CurrentTarget() if err != nil { - t.Skipf("current platform has no admitted npm package target: %v", err) + t.Fatalf("current platform has no admitted npm package target: %v", err) } binaryPath := filepath.Join(t.TempDir(), "agentic-proofkit") build := exec.Command("go", "build", "-o", binaryPath, "./cmd/agentic-proofkit") diff --git a/internal/tools/pypiregistry/main.go b/internal/tools/pypiregistry/main.go index 1d20464..d2be298 100644 --- a/internal/tools/pypiregistry/main.go +++ b/internal/tools/pypiregistry/main.go @@ -27,8 +27,11 @@ const ( pypiRegistryAttemptLimit = 24 pypiRegistryRetryDelay = 10 * time.Second + maxPyPIResponseBytes = 8 << 20 ) +var errPyPIResponseTooLarge = errors.New("pypi response exceeds byte limit") + type packageJSON struct { Name string `json:"name"` Version string `json:"version"` @@ -84,6 +87,14 @@ func (err pypiHTTPStatusError) Error() string { return fmt.Sprintf("pypi returned %s: %s", err.Status, err.Body) } +type pypiMissingWheelError struct { + Filename string +} + +func (err pypiMissingWheelError) Error() string { + return fmt.Sprintf("pypi release is missing wheel %s", err.Filename) +} + type registryArtifactSet struct { ArtifactKind string `json:"artifactKind"` AuthorityChannel string `json:"authorityChannel"` @@ -225,28 +236,37 @@ func fetchPyPIRelease(name string, version string) (pypiResponse, error) { if err != nil { return pypiResponse{}, err } + defer response.Body.Close() if response.StatusCode != http.StatusOK { body, _ := io.ReadAll(io.LimitReader(response.Body, 4096)) - _ = response.Body.Close() return pypiResponse{}, pypiHTTPStatusError{ StatusCode: response.StatusCode, Status: response.Status, Body: strings.TrimSpace(string(body)), } } - body, err := io.ReadAll(response.Body) - _ = response.Body.Close() + out, err := decodePyPIResponse(response.Body) if err != nil { return pypiResponse{}, err } - out, err := admission.DecodeTypedJSON[pypiResponse](bytes.NewReader(body), int64(len(body))) + return out, nil +} + +func decodePyPIResponse(reader io.Reader) (pypiResponse, error) { + body, err := io.ReadAll(io.LimitReader(reader, maxPyPIResponseBytes+1)) if err != nil { return pypiResponse{}, err } - return out, nil + if len(body) > maxPyPIResponseBytes { + return pypiResponse{}, errPyPIResponseTooLarge + } + return admission.DecodeTypedJSON[pypiResponse](bytes.NewReader(body), maxPyPIResponseBytes) } func retryablePyPIFetchError(err error) bool { + if errors.Is(err, errPyPIResponseTooLarge) { + return false + } var statusErr pypiHTTPStatusError if errors.As(err, &statusErr) { return statusErr.StatusCode == http.StatusNotFound || statusErr.StatusCode >= http.StatusInternalServerError @@ -255,7 +275,8 @@ func retryablePyPIFetchError(err error) bool { } func retryableRegistryEvidenceError(err error) bool { - return strings.Contains(err.Error(), "pypi release is missing wheel") + var missing pypiMissingWheelError + return errors.As(err, &missing) } func requireCandidatePlatformCompleteness(records []wheelRecord) error { @@ -337,7 +358,7 @@ func compareRegistryFiles(candidates pythonPackageSet, registry pypiResponse) ([ for _, candidate := range candidates.Packages { file, ok := filesByName[candidate.Filename] if !ok { - return nil, fmt.Errorf("pypi release is missing wheel %s", candidate.Filename) + return nil, pypiMissingWheelError{Filename: candidate.Filename} } if file.PackageType != "bdist_wheel" { return nil, fmt.Errorf("pypi file %s has package type %s", file.Filename, file.PackageType) diff --git a/internal/tools/pypiregistry/main_test.go b/internal/tools/pypiregistry/main_test.go index d07d44b..0785bc7 100644 --- a/internal/tools/pypiregistry/main_test.go +++ b/internal/tools/pypiregistry/main_test.go @@ -1,6 +1,7 @@ package main import ( + "errors" "os" "path/filepath" "strings" @@ -10,6 +11,20 @@ import ( "github.com/research-engineering/agentic-proofkit/internal/kernel/releaseplatform" ) +func TestDecodePyPIResponseEnforcesByteLimit(t *testing.T) { + exact := strings.Repeat(" ", maxPyPIResponseBytes-2) + "{}" + if _, err := decodePyPIResponse(strings.NewReader(exact)); err != nil { + t.Fatalf("exact-limit response rejected: %v", err) + } + oversized := strings.Repeat(" ", maxPyPIResponseBytes+1) + if _, err := decodePyPIResponse(strings.NewReader(oversized)); !errors.Is(err, errPyPIResponseTooLarge) { + t.Fatalf("oversized response error=%v, want byte-limit classification", err) + } + if retryablePyPIFetchError(errPyPIResponseTooLarge) { + t.Fatal("deterministically oversized response must not be retried") + } +} + func TestCompareRegistryFilesAcceptsMatchingWheels(t *testing.T) { target, ok := releaseplatform.TargetByPlatformSuffix("linux-x64") if !ok { diff --git a/internal/tools/pythonpackage/continuation_test.go b/internal/tools/pythonpackage/continuation_test.go index 5c539eb..e7a5867 100644 --- a/internal/tools/pythonpackage/continuation_test.go +++ b/internal/tools/pythonpackage/continuation_test.go @@ -35,7 +35,7 @@ func TestExactDisplayedRouteOperandsRejectsWhitespaceAndExpansionMutants(t *test func TestInstalledWheelContinuationUsesExactPythonModuleProfileWithoutNPM(t *testing.T) { if runtime.GOOS == "windows" { - t.Skip("Windows wheels are not supported") + t.Fatal("Windows wheels are not supported") } repositoryRoot, err := filepath.Abs(filepath.Join("..", "..", "..")) if err != nil { @@ -50,7 +50,7 @@ func runInstalledWheelContinuationWitness(t *testing.T, repositoryRoot string) { t.Helper() target, err := currentTarget() if err != nil { - t.Skipf("current platform has no admitted wheel target: %v", err) + t.Fatalf("current platform has no admitted wheel target: %v", err) } manifest, err := readPackageJSON() if err != nil { diff --git a/internal/tools/pythonpackage/metadata_test.go b/internal/tools/pythonpackage/metadata_test.go index d24bd77..b19dc5d 100644 --- a/internal/tools/pythonpackage/metadata_test.go +++ b/internal/tools/pythonpackage/metadata_test.go @@ -351,6 +351,18 @@ func TestVerifyWheelContentsAcceptsDarwinTagAtOrAboveMachOMinimum(t *testing.T) } } +func TestVerifyEmbeddedBinaryTargetRejectsFormatAndArchitectureMismatch(t *testing.T) { + arm64MachO := macho64WithMinimumMacOS(12, 0, 0) + darwinAMD64 := releaseTargets()[1] + if err := verifyEmbeddedBinaryTarget(darwinAMD64, arm64MachO); err == nil || !strings.Contains(err.Error(), "does not match target amd64") { + t.Fatalf("architecture mismatch error=%v", err) + } + linuxARM64 := releaseTargets()[2] + if err := verifyEmbeddedBinaryTarget(linuxARM64, arm64MachO); err == nil || !strings.Contains(err.Error(), "decode embedded ELF") { + t.Fatalf("format mismatch error=%v", err) + } +} + func TestMachOMinimumMacOSRejectsTruncatedBuildVersion(t *testing.T) { content := macho64WithMinimumMacOS(12, 0, 0) binary.LittleEndian.PutUint32(content[36:40], 8) diff --git a/internal/tools/pythonpackage/verify.go b/internal/tools/pythonpackage/verify.go index 2a9a492..5309f12 100644 --- a/internal/tools/pythonpackage/verify.go +++ b/internal/tools/pythonpackage/verify.go @@ -4,6 +4,7 @@ import ( "archive/zip" "bytes" "crypto/sha256" + "debug/elf" "debug/macho" "encoding/base64" "encoding/csv" @@ -192,12 +193,47 @@ func verifyWheelContents(path string, manifest packageJSON, target target, expec if fmt.Sprintf("%x", embeddedBinarySHA256[:]) != expectedBinarySHA256 { return fmt.Errorf("%s embedded binary sha256 mismatch", path) } + if err := verifyEmbeddedBinaryTarget(target, embeddedBinary); err != nil { + return fmt.Errorf("%s: %w", path, err) + } if err := verifyDarwinWheelMinimum(target, embeddedBinary); err != nil { return fmt.Errorf("%s: %w", path, err) } return verifyRecord(entries, distInfo+"/RECORD") } +func verifyEmbeddedBinaryTarget(target target, content []byte) error { + switch target.GOOS { + case "darwin": + file, err := macho.NewFile(bytes.NewReader(content)) + if err != nil { + return fmt.Errorf("decode embedded Mach-O: %w", err) + } + expected := map[string]macho.Cpu{ + "amd64": macho.CpuAmd64, + "arm64": macho.CpuArm64, + }[target.GOARCH] + if expected == 0 || file.Cpu != expected { + return fmt.Errorf("embedded Mach-O architecture %s does not match target %s", file.Cpu, target.GOARCH) + } + case "linux": + file, err := elf.NewFile(bytes.NewReader(content)) + if err != nil { + return fmt.Errorf("decode embedded ELF: %w", err) + } + expected := map[string]elf.Machine{ + "amd64": elf.EM_X86_64, + "arm64": elf.EM_AARCH64, + }[target.GOARCH] + if expected == elf.EM_NONE || file.Machine != expected { + return fmt.Errorf("embedded ELF architecture %s does not match target %s", file.Machine, target.GOARCH) + } + default: + return fmt.Errorf("unsupported embedded binary target OS %s", target.GOOS) + } + return nil +} + func verifyDarwinWheelMinimum(target target, content []byte) error { if target.GOOS != "darwin" { return nil diff --git a/package.json b/package.json index 95e6ce3..086012c 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,9 @@ ], "scripts": { "build": "go run ./internal/tools/packagebuild", - "check": "npm run npm:version && npm run source-hygiene && npm run command-contract:check && npm run command-family:check && npm run text-policy && npm run mermaid:check && npm run browser:check && npm run go:check && npm run package:artifact && npm run self:receipt && npm run self:coverage && npm run release:closeout", + "check": "npm run npm:version && npm run source-hygiene && npm run command-contract:check && npm run command-family:check && npm run text-policy && npm run mermaid:check && npm run go:check && npm run browser:check && npm run package:artifact && npm run self:receipt && npm run self:coverage && npm run release:closeout", "browser:check": "npm run browser:static-check && npm run browser:test", - "browser:static-check": "tsc -p tsconfig.browser.json && node --test scripts/browser-proof-execution.test.mjs scripts/browser-proof-inputs.test.mjs scripts/browser-selection-authority.test.mjs", + "browser:static-check": "tsc -p tsconfig.browser.json && node --test scripts/browser-proof-execution.test.mjs scripts/browser-proof-inputs.test.mjs scripts/browser-selection-authority.test.mjs scripts/stable-json.test.mjs", "browser:test": "go run ./internal/tools/browserproofverify --run", "command-family:check": "go run ./internal/tools/commandfamilygen --check", "go:actionlint": "go tool actionlint", diff --git a/proofkit/cli-contract.v2.json b/proofkit/cli-contract.v2.json index 7ed0cc0..6a16118 100644 --- a/proofkit/cli-contract.v2.json +++ b/proofkit/cli-contract.v2.json @@ -1340,7 +1340,7 @@ "rootType": "object", "closed": true, "rootDefinitionRef": "proofkit.external-consumer.input.v1.root-shape", - "rootDefinitionDigest": "sha256:8434bcea866d7e26d93fc896d88e16659252c6b5b7057a60525d9c1b64c1bf40", + "rootDefinitionDigest": "sha256:89b31d89ae46fcef5f5d90a64e1a38427dd048ff910fbd1c51937469468ca83f", "nativeSource": { "path": "internal/command/externalconsumer", "canonicalDigest": "sha256:2319bb14baed7ece382f022d3b397cdf241844a2d437716cc5098661486e9a25", @@ -2216,7 +2216,7 @@ "nativeSources": [ { "path": "internal/app", - "canonicalDigest": "sha256:9a9280f7e5803767e0edc5aa5431d9163becc9265d767b2cc1acb22f454b70ea", + "canonicalDigest": "sha256:b05dbafc64cccb87ef05a31f048096629ecdfe3cb3f9ad0e8d4131a3966e68ab", "evidenceClass": "source_checkout" }, { @@ -2541,7 +2541,7 @@ "rootDefinitionDigest": "sha256:7db0df3e9be3eba731937a4d887aa031cd70d3d5db36de3c630a1bced1d04bcf", "nativeSource": { "path": "internal/command/readinesscloseout", - "canonicalDigest": "sha256:8e706df865d00d2258328a6be9700a0200f856335b018209a63af5f06b0052d6", + "canonicalDigest": "sha256:87e9d83fbcd645f90dbd894af63d083629c1330fac60e3ec39698bffe01dc122", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -2569,7 +2569,7 @@ "rootDefinitionDigest": "sha256:0ef2b994e1ee6fed9ce10740c0464dda72c3df220a9453e7a6a221fba31cec29", "nativeSource": { "path": "internal/command/readinesscloseout", - "canonicalDigest": "sha256:8e706df865d00d2258328a6be9700a0200f856335b018209a63af5f06b0052d6", + "canonicalDigest": "sha256:87e9d83fbcd645f90dbd894af63d083629c1330fac60e3ec39698bffe01dc122", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -2816,7 +2816,7 @@ "rootType": "object", "closed": true, "rootDefinitionRef": "proofkit.registry-consumer.input.v1.root-shape", - "rootDefinitionDigest": "sha256:cc490b41d9f9e68c5121f81e702222ed3e717d462fa2059a0e0b76c0c82fb5e5", + "rootDefinitionDigest": "sha256:b9933ddbbca994fc3ea4d48da422ec5b09972ec47994a000ffbd31f46bc7cb9d", "nativeSource": { "path": "internal/command/registryconsumer", "canonicalDigest": "sha256:8b208777ccc022c5ea6b2a1d74366acd885ea0089a802999bc0a896c3b3a9c63", @@ -3113,7 +3113,7 @@ "rootDefinitionDigest": "sha256:7cb2357b227c2669b5322712ce6e032315e18b726dde8ead31b1feb29e14b418", "nativeSource": { "path": "internal/command/repoprofileadmission", - "canonicalDigest": "sha256:47c0521b8a99c2aa96016735995d700b555601d0dc3705b7248db6926e789b9f", + "canonicalDigest": "sha256:9a60318c4f1b673c79cf0fa55f589170256a16e18ec3c9b069ba567184caff6d", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -3140,7 +3140,7 @@ "rootDefinitionDigest": "sha256:acf8e7db39bfb1325d83c2cdfe68a7c243847d207e56691a3b2937b28f95d3e7", "nativeSource": { "path": "internal/command/repoprofileadmission", - "canonicalDigest": "sha256:47c0521b8a99c2aa96016735995d700b555601d0dc3705b7248db6926e789b9f", + "canonicalDigest": "sha256:9a60318c4f1b673c79cf0fa55f589170256a16e18ec3c9b069ba567184caff6d", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -3370,7 +3370,7 @@ "rootDefinitionDigest": "sha256:50a7bb1b477b6067d410cf22db7e7564a9742aa7f6a270642149d372f0156007", "nativeSource": { "path": "internal/command/requirementbrowser", - "canonicalDigest": "sha256:db68fadae3f68184d118cfdd39befedde9783cbcdd798cf56337b43fc0f7d018", + "canonicalDigest": "sha256:c968e0fd5294817ad367e5e9f80658133ef8118107135461a71b2b4a9c137795", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -3407,7 +3407,7 @@ "rootDefinitionDigest": "sha256:c2e7d851c7928560d4267fe85ebc0ae61e33c4c0ef15f7f7b01deb73bb80eda7", "nativeSource": { "path": "internal/command/requirementbrowser", - "canonicalDigest": "sha256:db68fadae3f68184d118cfdd39befedde9783cbcdd798cf56337b43fc0f7d018", + "canonicalDigest": "sha256:c968e0fd5294817ad367e5e9f80658133ef8118107135461a71b2b4a9c137795", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -3454,7 +3454,7 @@ "rootDefinitionDigest": "sha256:41bc233c96bd468bc96eeb0447e037cacdbcf92bb04161052303e8747e4282f2", "nativeSource": { "path": "internal/command/requirementcontext", - "canonicalDigest": "sha256:120f236dc1e9e11e5cecb6237bf727fe513b75e83783f52d31872c6c2b20ae7e", + "canonicalDigest": "sha256:f81c94b2ff570bac1e3fcf1d32ee25dc36e6b3baacf2c63744f6824ee77cade2", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -3494,7 +3494,7 @@ "rootDefinitionDigest": "sha256:cdadd7b589d682a122cfe2b801f001a3b22a2269aa4977ebe073932d11e1f816", "nativeSource": { "path": "internal/command/requirementcontext", - "canonicalDigest": "sha256:120f236dc1e9e11e5cecb6237bf727fe513b75e83783f52d31872c6c2b20ae7e", + "canonicalDigest": "sha256:f81c94b2ff570bac1e3fcf1d32ee25dc36e6b3baacf2c63744f6824ee77cade2", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -3541,7 +3541,7 @@ "rootDefinitionDigest": "sha256:43852cf1a52b3c1f000ec95e460fbad157a6b6a39958d20f81c9e1e4d31e3c6f", "nativeSource": { "path": "internal/command/requirementcontext", - "canonicalDigest": "sha256:120f236dc1e9e11e5cecb6237bf727fe513b75e83783f52d31872c6c2b20ae7e", + "canonicalDigest": "sha256:f81c94b2ff570bac1e3fcf1d32ee25dc36e6b3baacf2c63744f6824ee77cade2", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -3579,7 +3579,7 @@ "rootDefinitionDigest": "sha256:61f7fd43e2e9da5bde9b61bb86e02cbf337a43c6891ca888c1d42936e5b16456", "nativeSource": { "path": "internal/command/requirementcontext", - "canonicalDigest": "sha256:120f236dc1e9e11e5cecb6237bf727fe513b75e83783f52d31872c6c2b20ae7e", + "canonicalDigest": "sha256:f81c94b2ff570bac1e3fcf1d32ee25dc36e6b3baacf2c63744f6824ee77cade2", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -4091,7 +4091,7 @@ "rootType": "object", "closed": true, "rootDefinitionRef": "proofkit.requirement-proof-source-set.input.v1.root-shape", - "rootDefinitionDigest": "sha256:8003f7feca3b82413cbc2bfdbc3b754508ec3d7e1706df54431b6332d19ec5fd", + "rootDefinitionDigest": "sha256:cd406c4aba90faa0fa2b132d4c50af85578ec79a6ad1d89bc997ea027d6def05", "nativeSource": { "path": "internal/command/requirementproofsourceset", "canonicalDigest": "sha256:bd5377b5ec6b2a5a506e2702ed8dacb9a25908aa9f63002a4cf88f63f4641ba7", @@ -5014,7 +5014,7 @@ "rootType": "object", "closed": true, "rootDefinitionRef": "proofkit.secret-scan.input.v1.root-shape", - "rootDefinitionDigest": "sha256:6477a651d88632cbdd4cdde0877b95faa1efaeaa16ec8d16a913313517d591ce", + "rootDefinitionDigest": "sha256:6df7c84b104a318b2f6b04b662c3dfb54b16f4fb339cfb5f0ba6ee56485fae8f", "nativeSource": { "path": "internal/command/secretscan", "canonicalDigest": "sha256:7fe8eb1fe43973b321fce0edce337b565d4a51e86e244bce9c3a57039209b0e3", @@ -5169,7 +5169,7 @@ "rootType": "object", "closed": true, "rootDefinitionRef": "proofkit.selective-gate-obligation-decision-input.input.v1.root-shape", - "rootDefinitionDigest": "sha256:f488e84a8a588593d41cdbea8991fbe549239580292db573d215c8a4725ad2ee", + "rootDefinitionDigest": "sha256:394c4523f385351794db259b7a7d365d0852ee22ec38695b1336f6559f823a15", "nativeSource": { "path": "internal/command/selectivegateevidence", "canonicalDigest": "sha256:ddee1b6e09f88546d237fe32b4f4c98b62406efe0556273f382d1e6be577c4ef", @@ -5341,7 +5341,7 @@ "rootDefinitionDigest": "sha256:3c842174dff5361e7f83166469b832805e05aa314b073c16234b5b64e346281e", "nativeSource": { "path": "internal/app", - "canonicalDigest": "sha256:9a9280f7e5803767e0edc5aa5431d9163becc9265d767b2cc1acb22f454b70ea", + "canonicalDigest": "sha256:b05dbafc64cccb87ef05a31f048096629ecdfe3cb3f9ad0e8d4131a3966e68ab", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -5370,7 +5370,7 @@ "rootDefinitionDigest": "sha256:0ea95e277ebe44cd2de42c29b47c38686ac0b6b390d8965367437b3fe138e209", "nativeSource": { "path": "internal/app", - "canonicalDigest": "sha256:9a9280f7e5803767e0edc5aa5431d9163becc9265d767b2cc1acb22f454b70ea", + "canonicalDigest": "sha256:b05dbafc64cccb87ef05a31f048096629ecdfe3cb3f9ad0e8d4131a3966e68ab", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": { @@ -8476,48 +8476,14 @@ "variants": [ { "allowedFields": [ - "binarySmokeProbeRuleId", - "nonClaims", - "npmIntegrity", - "npmShasum", - "packMetadataPath", - "packMetadataSha256", - "packageName", - "packageVersion", - "pilotId", - "pilotMode", - "releaseAuthorityInput", - "rollback", - "schemaVersion", - "sourceArtifactName", - "sourceCommit", - "sourceRepository", - "sourceWorkflowRun", - "tarballPath", - "tarballSha256", - "witnessPlan" + "evidence", + "input", + "schemaVersion" ], "requiredFields": [ - "binarySmokeProbeRuleId", - "nonClaims", - "npmIntegrity", - "npmShasum", - "packMetadataPath", - "packMetadataSha256", - "packageName", - "packageVersion", - "pilotId", - "pilotMode", - "releaseAuthorityInput", - "rollback", - "schemaVersion", - "sourceArtifactName", - "sourceCommit", - "sourceRepository", - "sourceWorkflowRun", - "tarballPath", - "tarballSha256", - "witnessPlan" + "evidence", + "input", + "schemaVersion" ], "rootKind": "object", "variantId": "01-root", @@ -8527,7 +8493,7 @@ } ] }, - "canonicalDigest": "sha256:8434bcea866d7e26d93fc896d88e16659252c6b5b7057a60525d9c1b64c1bf40" + "canonicalDigest": "sha256:89b31d89ae46fcef5f5d90a64e1a38427dd048ff910fbd1c51937469468ca83f" }, { "definitionId": "proofkit.external-consumer.output.v1.root-shape", @@ -10653,34 +10619,13 @@ "variants": [ { "allowedFields": [ - "consumerId", - "dependencyName", - "dependencySpec", - "nonClaims", - "packageName", - "packageVersion", - "registryUrl", - "releaseAuthorityInput", - "rollbackVersionPin", - "schemaVersion", - "tarballFileName", - "tarballIntegrity", - "tarballShasum" + "input", + "proof", + "schemaVersion" ], "requiredFields": [ - "consumerId", - "dependencyName", - "dependencySpec", - "nonClaims", - "packageName", - "packageVersion", - "registryUrl", - "releaseAuthorityInput", - "rollbackVersionPin", - "schemaVersion", - "tarballFileName", - "tarballIntegrity", - "tarballShasum" + "input", + "schemaVersion" ], "rootKind": "object", "variantId": "01-root", @@ -10690,7 +10635,7 @@ } ] }, - "canonicalDigest": "sha256:cc490b41d9f9e68c5121f81e702222ed3e717d462fa2059a0e0b76c0c82fb5e5" + "canonicalDigest": "sha256:b9933ddbbca994fc3ea4d48da422ec5b09972ec47994a000ffbd31f46bc7cb9d" }, { "definitionId": "proofkit.registry-consumer.output.v1.root-shape", @@ -12176,7 +12121,6 @@ ], "requiredFields": [ "canonicalEnvelope", - "projection", "sourceSet", "sources" ], @@ -12188,7 +12132,7 @@ } ] }, - "canonicalDigest": "sha256:8003f7feca3b82413cbc2bfdbc3b754508ec3d7e1706df54431b6332d19ec5fd" + "canonicalDigest": "sha256:cd406c4aba90faa0fa2b132d4c50af85578ec79a6ad1d89bc997ea027d6def05" }, { "definitionId": "proofkit.requirement-proof-source-set.output.v1.root-shape", @@ -13270,8 +13214,7 @@ "files", "nonClaims", "reportId", - "schemaVersion", - "suppressions" + "schemaVersion" ], "rootKind": "object", "variantId": "01-root", @@ -13281,7 +13224,7 @@ } ] }, - "canonicalDigest": "sha256:6477a651d88632cbdd4cdde0877b95faa1efaeaa16ec8d16a913313517d591ce" + "canonicalDigest": "sha256:6df7c84b104a318b2f6b04b662c3dfb54b16f4fb339cfb5f0ba6ee56485fae8f" }, { "definitionId": "proofkit.secret-scan.output.v1.root-shape", @@ -13469,23 +13412,19 @@ "variants": [ { "allowedFields": [ - "evidenceClass", - "evidenceId", + "commandRoutes", + "decisionId", + "evidence", "nonClaims", - "plan", - "preexistingFailures", - "producerAdmission", - "receipts", + "receiptCurrentnessScopeAdmission", + "receiptTrustClassAdmission", "schemaVersion" ], "requiredFields": [ - "evidenceClass", - "evidenceId", + "commandRoutes", + "decisionId", + "evidence", "nonClaims", - "plan", - "preexistingFailures", - "producerAdmission", - "receipts", "schemaVersion" ], "rootKind": "object", @@ -13496,7 +13435,7 @@ } ] }, - "canonicalDigest": "sha256:f488e84a8a588593d41cdbea8991fbe549239580292db573d215c8a4725ad2ee" + "canonicalDigest": "sha256:394c4523f385351794db259b7a7d365d0852ee22ec38695b1336f6559f823a15" }, { "definitionId": "proofkit.selective-gate-obligation-decision-input.output.v1.root-shape", diff --git a/proofkit/witness-plan.json b/proofkit/witness-plan.json index a18a44a..9759b20 100644 --- a/proofkit/witness-plan.json +++ b/proofkit/witness-plan.json @@ -788,6 +788,8 @@ "scripts/browser-proof-inputs.test.mjs", "scripts/browser-runtime-proof-inputs.v1.json", "scripts/browser-selection-authority.test.mjs", + "scripts/stable-json.mjs", + "scripts/stable-json.test.mjs", "scripts/write-browser-proof.mjs", "tests/browser", "tsconfig.browser.json" diff --git a/scripts/browser-runtime-proof-inputs.v1.json b/scripts/browser-runtime-proof-inputs.v1.json index c58cc30..04ad94f 100644 --- a/scripts/browser-runtime-proof-inputs.v1.json +++ b/scripts/browser-runtime-proof-inputs.v1.json @@ -16,6 +16,8 @@ "scripts/browser-proof-inputs.mjs", "scripts/browser-proof-inputs.test.mjs", "scripts/browser-selection-authority.test.mjs", + "scripts/stable-json.mjs", + "scripts/stable-json.test.mjs", "tsconfig.browser.json" ] } diff --git a/scripts/stable-json.mjs b/scripts/stable-json.mjs new file mode 100644 index 0000000..f92c471 --- /dev/null +++ b/scripts/stable-json.mjs @@ -0,0 +1,24 @@ +export function stableJSONStringify(value) { + switch (typeof value) { + case "string": + return quote(value); + case "boolean": + return value ? "true" : "false"; + case "number": + if (!Number.isFinite(value)) throw new Error("stable JSON numbers must be finite"); + return JSON.stringify(value); + case "object": + if (value === null) return "null"; + if (Array.isArray(value)) return `[${value.map(stableJSONStringify).join(",")}]`; + return `{${Object.keys(value).sort().map((key) => + `${quote(key)}:${stableJSONStringify(value[key])}`).join(",")}}`; + default: + throw new Error(`unsupported stable JSON value type: ${typeof value}`); + } +} + +function quote(value) { + return JSON.stringify(value) + .replaceAll("\u2028", "\\u2028") + .replaceAll("\u2029", "\\u2029"); +} diff --git a/scripts/stable-json.test.mjs b/scripts/stable-json.test.mjs new file mode 100644 index 0000000..8a5c82d --- /dev/null +++ b/scripts/stable-json.test.mjs @@ -0,0 +1,16 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import {stableJSONStringify} from "./stable-json.mjs"; + +test("stableJSONStringify sorts object keys without HTML escaping", () => { + assert.equal( + stableJSONStringify({z: "<&>", a: [{y: "\u2028", x: 1}]}), + `{"a":[{"x":1,"y":"\\u2028"}],"z":"<&>"}`, + ); +}); + +test("stableJSONStringify rejects unsupported values", () => { + assert.throws(() => stableJSONStringify({missing: undefined}), /unsupported stable JSON value type/); + assert.throws(() => stableJSONStringify(Number.NaN), /must be finite/); +}); diff --git a/scripts/workflow_source_oracles_test.go b/scripts/workflow_source_oracles_test.go index ac9adca..9a402df 100644 --- a/scripts/workflow_source_oracles_test.go +++ b/scripts/workflow_source_oracles_test.go @@ -2,6 +2,7 @@ package main import ( "crypto/sha256" + "encoding/json" "errors" "fmt" "os" @@ -264,6 +265,55 @@ func TestWorkflowExternalActionsUseFullCommitSHAs(t *testing.T) { } } +func TestRootCheckRetainsRequiredProofGates(t *testing.T) { + raw, err := os.ReadFile(filepath.Join("..", "package.json")) + if err != nil { + t.Fatal(err) + } + var manifest struct { + Scripts map[string]string `json:"scripts"` + } + if err := json.Unmarshal(raw, &manifest); err != nil { + t.Fatal(err) + } + if err := validateRootCheckScript(manifest.Scripts["check"]); err != nil { + t.Fatal(err) + } + for _, removed := range []string{"npm run go:check", "npm run browser:check", "npm run package:artifact"} { + mutant := strings.Replace(manifest.Scripts["check"], " && "+removed, "", 1) + if err := validateRootCheckScript(mutant); err == nil { + t.Fatalf("check oracle admitted removal of %q", removed) + } + } +} + +func validateRootCheckScript(script string) error { + steps := strings.Split(script, " && ") + indexes := map[string]int{} + for index, step := range steps { + if _, duplicate := indexes[step]; duplicate { + return fmt.Errorf("root check contains duplicate step %q", step) + } + indexes[step] = index + } + for _, required := range []string{ + "npm run go:check", + "npm run browser:check", + "npm run package:artifact", + "npm run self:receipt", + "npm run self:coverage", + "npm run release:closeout", + } { + if _, ok := indexes[required]; !ok { + return fmt.Errorf("root check omits required proof gate %q", required) + } + } + if indexes["npm run go:check"] > indexes["npm run browser:check"] { + return errors.New("root check must run deterministic Go gates before browser gates") + } + return nil +} + func TestExistingReleasePathIsReadOnlyAndFailsOnDrift(t *testing.T) { workflow := readWorkflowForTest(t, filepath.Join("..", ".github", "workflows", "release.yml")) job := workflow.Jobs["release-assets"] diff --git a/scripts/write-browser-proof.mjs b/scripts/write-browser-proof.mjs index 4a15a82..8056c27 100644 --- a/scripts/write-browser-proof.mjs +++ b/scripts/write-browser-proof.mjs @@ -4,6 +4,7 @@ import {writeFileSync} from "node:fs"; import {executeBrowserProof} from "./browser-proof-execution.mjs"; import {isSafeRepoPath, loadBrowserProofInputResolution} from "./browser-proof-inputs.mjs"; +import {stableJSONStringify} from "./stable-json.mjs"; const testCommand = ["node_modules/@playwright/test/cli.js", "test"]; const resolution = loadBrowserProofInputResolution(); @@ -25,7 +26,7 @@ const execution = await executeBrowserProof({ }); const {assets, projects, testResult} = execution; const inputResolution = {serverTarget: resolution.serverTarget, writerPath: resolution.writerPath}; -const inputDigest = createHash("sha256").update(JSON.stringify({assets, inputResolution})).digest("hex"); +const inputDigest = createHash("sha256").update(stableJSONStringify({assets, inputResolution})).digest("hex"); const engines = projects.map(({browserName, browserVersion}) => ({name: browserName, version: browserVersion})); const sourceRevision = execFileSync("git", ["rev-parse", "HEAD"], {encoding: "utf8"}).trim(); const sourceTreeState = execFileSync("git", ["status", "--porcelain=v1", "--untracked-files=all"], {encoding: "utf8"}).trim() === "" ? "clean" : "dirty"; diff --git a/tests/browser/workspace.spec.mjs b/tests/browser/workspace.spec.mjs index 522733c..b8ccdc1 100644 --- a/tests/browser/workspace.spec.mjs +++ b/tests/browser/workspace.spec.mjs @@ -107,6 +107,15 @@ async function expectCSS(locator, properties) { expect(completed).toEqual(plan); } +async function expectAllCSS(locator, properties) { + const propertyNames = Object.keys(properties); + const actual = await locator.evaluateAll((elements, names) => elements.map((element) => { + const computed = window.getComputedStyle(element); + return Object.fromEntries(names.map((name) => [name, computed.getPropertyValue(name)])); + }), propertyNames); + expect(actual).toEqual(actual.map(() => properties)); +} + function assertAssertionPlanFalsifiers() { const first = Object.freeze({property: "opacity", value: "1"}); const second = Object.freeze({property: "filter", value: "none"}); @@ -153,31 +162,29 @@ async function expectVisibleTable(table, caption, headers, rows) { for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) { const row = bodyRows.nth(rowIndex); await expect(row).toBeVisible(); - await expectCSS(row, commonStyles); await expect(row.locator(":scope > td")).toHaveText(rows[rowIndex]); } + await expectAllCSS(bodyRows, commonStyles); const textElements = table.locator("caption, th, td"); const textCount = 1 + headers.length + rows.reduce((count, row) => count + row.length, 0); await expect(textElements).toHaveCount(textCount); await expect(table.locator("caption:visible, th:visible, td:visible")).toHaveCount(textCount); - for (let index = 0; index < textCount; index += 1) { - await expectCSS(textElements.nth(index), { - opacity: "1", - color: "rgb(23, 32, 51)", - "font-size": "16px", - "font-size-adjust": "none", - "-webkit-text-security": "none", - "text-transform": "none", - filter: "none", - "clip-path": "none", - "mask-image": "none", - "content-visibility": "visible", - zoom: "1", - "animation-name": "none", - "transition-duration": "0s", - "transition-delay": "0s", - }); - } + await expectAllCSS(textElements, { + opacity: "1", + color: "rgb(23, 32, 51)", + "font-size": "16px", + "font-size-adjust": "none", + "-webkit-text-security": "none", + "text-transform": "none", + filter: "none", + "clip-path": "none", + "mask-image": "none", + "content-visibility": "visible", + zoom: "1", + "animation-name": "none", + "transition-duration": "0s", + "transition-delay": "0s", + }); } test.beforeEach(async ({browser, browserName, channel, connectOptions, launchOptions}, testInfo) => { @@ -652,6 +659,7 @@ test("handoff packet output never creates a zero-value keyboard stop", async ({b }); test("workspace renders admitted views and creates a keyboard-authorized handoff", async ({baseURL, browserName, page}) => { + test.setTimeout(60_000); assertAssertionPlanFalsifiers(); const consoleErrors = []; page.on("console", (message) => { From 138743ed15747dda3f2753c9eaf875d7d8523e15 Mon Sep 17 00:00:00 2001 From: iperev Date: Thu, 30 Jul 2026 21:22:15 +0200 Subject: [PATCH 2/2] fix: keep all requirement pages reachable --- internal/app/cli_contract_test.go | 2 +- internal/app/command_contract_generated.go | 4 +-- .../requirementbrowser/http_handler.go | 4 +-- .../requirementbrowser/http_handler_test.go | 25 +++++++++++++++++++ .../stackpreset/preset_ids_generated.go | 2 +- proofkit/cli-contract.v2.json | 4 +-- 6 files changed, 33 insertions(+), 8 deletions(-) diff --git a/internal/app/cli_contract_test.go b/internal/app/cli_contract_test.go index e2aa84a..613aa3e 100644 --- a/internal/app/cli_contract_test.go +++ b/internal/app/cli_contract_test.go @@ -22,7 +22,7 @@ import ( ) const ( - cliContractPublicABISHA256 = "4f305627f56ea031fc501b872ce9e20afa3e6b7b3af4fe20c48f787693198fa4" + cliContractPublicABISHA256 = "8c1ed8d811ee9421a773647cc6255dcd233af6d5ab24c2c7d76bca06760da723" maxAggregateFileReadBytesForContractTest = 64 << 20 maxPackageManifestBytesForContractTest = 256 << 10 maxSourceFileBytesForContractTest = 8 << 20 diff --git a/internal/app/command_contract_generated.go b/internal/app/command_contract_generated.go index 17b504b..49410f3 100644 --- a/internal/app/command_contract_generated.go +++ b/internal/app/command_contract_generated.go @@ -1,7 +1,7 @@ // Code generated by internal/tools/commandcontractgen; DO NOT EDIT. package app -const commandContractSourceSHA256 = "611956ab74fd69cdede8fd37947c6c7ed05f3bd95ad6cc2f5249cbddf777d7d3" +const commandContractSourceSHA256 = "82d4ba762c0773ab5b2ba400eb488b201ea4bbb87fae395968b0e1aba3dd022f" type generatedCommandContractMetadata struct { InputContractSHA256 string @@ -54,7 +54,7 @@ var generatedCommandContractMetadataByName = map[string]generatedCommandContract "repo-profile-admission": {InputContractSHA256: "sha256:3a7331d66195dbdc9f672d380efe8fdb9d1d2e36a764b8bc912dccdd81b0e965", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.repo-profile-admission.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:36d2116fa144aa86d7b9d0ac59b89ad04fb97c85a11f3f65efb7311506761fbd", FlagChoices: map[string][]string{}}, "requirement-authoring-plan": {InputContractSHA256: "sha256:9167f6ea1e888c196c14799f30a7657cf7e74a4af53f15ef61b44b9ae82cc5ba", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-authoring-plan.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:6cca126c3295537f0c7a73892e8c8778bbd10e442290e75dd45b833373312a46", FlagChoices: map[string][]string{}}, "requirement-bindings": {InputContractSHA256: "sha256:c9fc55b5b8d67849adb8a10b441b73f33a9eb1b6b6afeee7ec698874e1f9cd50", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-bindings.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:f61039fafcdb2ced7655c1be0bd9b46d689878f27aebdf12975c8e9f32cbd14e", FlagChoices: map[string][]string{}}, - "requirement-browser-server": {InputContractSHA256: "sha256:aee38d90bd8a58137200da3ac277c6d5ec9171c75b499bafd48ad31bb793b934", InputSchemaSummary: []string{"workspace mode: schemaVersion=2", "workspace mode: workspaceId", "workspace mode: context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter", "workspace mode: diffInput=proofkit.requirement-semantic-diff-input schemaVersion=2 (optional)", "workspace mode: graphInput=proofkit.requirement-traceability-graph-input schemaVersion=2 (optional)", "--session-mode values: browse|one-shot-question", "one-shot-question requires --view workspace --serve --open", "--session-timeout-seconds is 1..7200 and requires one-shot-question", "source|proof|coverage|spec-tree modes retain their owner input contracts", "root-shape-only definition proofkit.requirement-browser-server.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:0c95a66fdb2543c8839cff5e71ed5f027f99fb802dd32699a4e7d1eb53c99cdf", FlagChoices: map[string][]string{}}, + "requirement-browser-server": {InputContractSHA256: "sha256:a472df133dd313c67e269699fc27e934d22689aab96aba44d399a111d60e13f3", InputSchemaSummary: []string{"workspace mode: schemaVersion=2", "workspace mode: workspaceId", "workspace mode: context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter", "workspace mode: diffInput=proofkit.requirement-semantic-diff-input schemaVersion=2 (optional)", "workspace mode: graphInput=proofkit.requirement-traceability-graph-input schemaVersion=2 (optional)", "--session-mode values: browse|one-shot-question", "one-shot-question requires --view workspace --serve --open", "--session-timeout-seconds is 1..7200 and requires one-shot-question", "source|proof|coverage|spec-tree modes retain their owner input contracts", "root-shape-only definition proofkit.requirement-browser-server.input.v2.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:04400dfb27b7a8b62a66dd8db588da60dd27158b061cd863ce28bafda5e77d95", FlagChoices: map[string][]string{}}, "requirement-context-compose": {InputContractSHA256: "sha256:847743017c9af0cf02f2403082bb1a45e6e18670d9ffecdc568e92ec62240927", InputSchemaSummary: []string{"schemaVersion=1", "catalogId", "specTree.path", "requirementSources[] (non-empty)", "requirementSources[].nodeId", "requirementSources[].path", "expectedSourceDigest (optional sha256 ref)", "proofBinding.path (optional)", "coverage.path (optional)", "exact catalog paths only; no discovery", "root-shape-only definition proofkit.requirement-context-compose.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:9e6bb924f291b56c8da5162d6d8584c2b90313fcec62a8e3b9f2e68e9fc123a5", FlagChoices: map[string][]string{}}, "requirement-context-slice": {InputContractSHA256: "sha256:12576719455258fdaf1e9c2c8f982758e848bce1886f43188f458cf01a3864f8", InputSchemaSummary: []string{"schemaVersion=1", "sliceId", "context=proofkit.requirement-context schemaVersion=2 with strict v1 adapter", "query.profile=routing|specification|proof|coverage|review", "query.nodeIds[]|requirementIds[]|ownerIds[]|lifecycleStates[]", "query.maxDepth=0..512", "query.maxNodes=1..4096", "query.maxRequirements=1..16384", "root-shape-only definition proofkit.requirement-context-slice.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:bca73df6487d9253925d1ce4e209d16af75b1bbd46fc72e3f2982be3a2d9b42e", FlagChoices: map[string][]string{}}, "requirement-coverage-input-compose": {InputContractSHA256: "sha256:26e46a1438f5633a835f4afaef293bed5417b13f4cd4c0a752d02659fcd094c6", InputSchemaSummary: []string{"schemaVersion=1", "root-shape-only definition proofkit.requirement-coverage-input-compose.input.v1.root-shape; nested fields, types, and cardinalities are non-claims"}, OutputContractSHA256: "sha256:4e246b49fd99038d4b1df9ce484ef2214c3129769df2aaf67b398367a451dd86", FlagChoices: map[string][]string{}}, diff --git a/internal/command/requirementbrowser/http_handler.go b/internal/command/requirementbrowser/http_handler.go index a131c87..6553470 100644 --- a/internal/command/requirementbrowser/http_handler.go +++ b/internal/command/requirementbrowser/http_handler.go @@ -268,8 +268,8 @@ func admitProjectionQuery(raw any) (projectionQuery, error) { } if record["offset"] != nil { query.Offset, err = nonNegativeJSONInteger(record["offset"], "browser projection query offset") - if err != nil || query.Offset > 20_000 { - return projectionQuery{}, fmt.Errorf("browser projection query offset must be between 0 and 20000") + if err != nil { + return projectionQuery{}, err } } if record["maxRecords"] != nil { diff --git a/internal/command/requirementbrowser/http_handler_test.go b/internal/command/requirementbrowser/http_handler_test.go index 93aea14..9fc562f 100644 --- a/internal/command/requirementbrowser/http_handler_test.go +++ b/internal/command/requirementbrowser/http_handler_test.go @@ -1,7 +1,9 @@ package requirementbrowser import ( + "encoding/json" "errors" + "fmt" "net/http" "net/http/httptest" "testing" @@ -48,3 +50,26 @@ func TestWorkspaceRequestAdmissionIsBounded(t *testing.T) { } releaseWorkspaceRequest(requests) } + +func TestProjectionQueryKeepsEveryAdmittedRequirementPageReachable(t *testing.T) { + const finalOffset = 20_224 + requirements := make([]any, finalOffset+1) + for index := range requirements { + requirements[index] = map[string]any{"requirementId": fmt.Sprintf("REQ-%05d", index)} + } + query, err := admitProjectionQuery(map[string]any{ + "maxRecords": json.Number("256"), + "offset": json.Number("20224"), + }) + if err != nil { + t.Fatalf("admit final reachable page: %v", err) + } + projection, state := requirementWindow(requirements, query) + if state != "partial_with_omissions" || projection["selectedRequirementCount"] != 1 { + t.Fatalf("final page state=%s projection=%#v", state, projection) + } + selected := projection["requirements"].([]any) + if got := selected[0].(map[string]any)["requirementId"]; got != "REQ-20224" { + t.Fatalf("final requirement=%v, want REQ-20224", got) + } +} diff --git a/internal/command/stackpreset/preset_ids_generated.go b/internal/command/stackpreset/preset_ids_generated.go index 5d7d057..10476dd 100644 --- a/internal/command/stackpreset/preset_ids_generated.go +++ b/internal/command/stackpreset/preset_ids_generated.go @@ -1,6 +1,6 @@ // Code generated by internal/tools/commandcontractgen; DO NOT EDIT. package stackpreset -const presetContractSourceSHA256 = "611956ab74fd69cdede8fd37947c6c7ed05f3bd95ad6cc2f5249cbddf777d7d3" +const presetContractSourceSHA256 = "82d4ba762c0773ab5b2ba400eb488b201ea4bbb87fae395968b0e1aba3dd022f" var presetIDs = []string{"agentic_runtime_repo", "generated_docs_contract_repo", "python_service", "python_typescript_service", "typescript_monorepo", "typescript_workspace"} diff --git a/proofkit/cli-contract.v2.json b/proofkit/cli-contract.v2.json index 6a16118..1255a38 100644 --- a/proofkit/cli-contract.v2.json +++ b/proofkit/cli-contract.v2.json @@ -3370,7 +3370,7 @@ "rootDefinitionDigest": "sha256:50a7bb1b477b6067d410cf22db7e7564a9742aa7f6a270642149d372f0156007", "nativeSource": { "path": "internal/command/requirementbrowser", - "canonicalDigest": "sha256:c968e0fd5294817ad367e5e9f80658133ef8118107135461a71b2b4a9c137795", + "canonicalDigest": "sha256:7d85d0f51193cd3d42fbf19eb4dad4baadf5e5a050be9ce1363bf3e92dbdfcf2", "evidenceClass": "source_checkout" }, "nativeAdmissionWitnessSelector": { @@ -3407,7 +3407,7 @@ "rootDefinitionDigest": "sha256:c2e7d851c7928560d4267fe85ebc0ae61e33c4c0ef15f7f7b01deb73bb80eda7", "nativeSource": { "path": "internal/command/requirementbrowser", - "canonicalDigest": "sha256:c968e0fd5294817ad367e5e9f80658133ef8118107135461a71b2b4a9c137795", + "canonicalDigest": "sha256:7d85d0f51193cd3d42fbf19eb4dad4baadf5e5a050be9ce1363bf3e92dbdfcf2", "evidenceClass": "source_checkout" }, "nativeOutputWitnessSelector": {