test(crypto)+ci: sweep the at-rest assertions off the frozen v1 prefix, and pin the release toolchain - #48
Merged
Merged
Conversation
… frozen v1 prefix `[store].aad_bind` now defaults True (#38), so the default at-rest format is mfenc:v2. 41 test assertions anchored on `PREFIX` ("mfenc:v1:") whose actual claim is "this value is enciphered at rest at all" — a v1-only spelling that a v2 value silently fails. They move to the version-agnostic `MARKER_PREFIX` ("mfenc:"), which is the same anchor the store's own find-all/migration `mfenc:%` LIKE patterns and `is_encrypted()` already use. Evidence, not assertion. Under the ASVS correctness net `MEFOR_TEST_FORCE_AAD_BIND=1` (conftest forces every AesGcmCipher to the v2 writer), the swept files went 9 failed -> 1 failed. The one remaining failure is `test_store_aad_binding::test_v1_rows_still_read_under_aad_bind`, a site this commit deliberately does NOT touch: it fails on both branches because forcing v2 destroys its v1 premise, which is exactly what makes it the liveness receipt that the flag is live. DELIBERATELY LEFT as v1 (CRYPTO-1 / ADR 0019 coverage a sweep would DELETE), each now carrying a comment saying so — their absence is what made this dangerous to review: - the frozen-writer set (test_store_encryption: the _v1_blob oracle, the byte-identity gate, test_default_writer_is_v1_not_v2) - premise assertions whose point is "the row really IS v1" so a dual-read/no-rotation proof cannot go vacuous (test_v2_active_decrypts_v1_without_rotation, test_store_aad_binding, test_keyprovider x2, test_keyprovider_vault) - field-POSITION pins, where "mfenc:<fp>:" is a string no writer emits, so widening makes the positive unsatisfiable and the negative unfailable (test_key_id_is_a_fingerprint_not_zero, the legacy key_id='0' fixture) - undecryptable-payload injections (test_batch_claim_fifo, test_claim_fifo_heads): the marker version must be one the cipher DISPATCHES on, or the poison takes the unknown-version branch instead of the base64 failure the test is about Three checks got strictly stronger rather than merely wider: - the rotation assertion now anchors on `cipher.active_marker_prefix`, keeping the "re-encrypted under the ACTIVE key" half of the proof that a bare MARKER_PREFIX would have dropped - three NEGATIVE leak/plaintext checks (`not startswith` / `not in`) now exclude EVERY marker version; as v1-only they would have passed silently on a leaked or already-encrypted v2 value - test_ack_sent_store's manual decrypt now passes the cell AAD the store wrote with. Pre-existing and previously MASKED by the v1 assertion failing first; unmasked once the sweep let the test reach it. Harmless on v1 (that reader ignores the caller's aad by design). Comments naming "v1"/"the v1 marker" at swept sites were updated so they no longer lie. Verification (SQLite leg, from the worktree): 9135 passed, 816 skipped, 0 failed; ruff check + ruff format --check clean. Non-vacuity proven by injecting one regression (AesGcmCipher.encrypt returns plaintext): two swept assertions FAIL on a plaintext at-rest value and pass without it.
…inst silent rot Five installs on the release path resolved whatever PyPI served at tag time (Scorecard PinnedDependenciesID; ADR 0034 §3). The `release` job holds contents/id-token/attestations: write and `release-harness` holds contents/id-token: write, so these ran with the OIDC identity that signs and publishes. The sharp one is `sigstore`: its step carries no `if:`, so it runs on every tag AND every dispatch, and the very next command signs the wheel, sdist, SBOM and VEX with that identity. release.yml sigstore -> ==4.4.0 release.yml pip + build (x2: engine and harness jobs) -> ==26.1.2 / ==1.5.0 release.yml cyclonedx-bom -> ~=7.3.1 release.yml packaging (x2) -> ==$PKG_PIN, derived from constraints.lock security.yml pip + cyclonedx-bom -> ==26.1.2 / ~=7.3.1 (keeps it the true SBOM twin) Three judgement calls, none of them the obvious choice: 1. sigstore 4.4.0, NOT the newer 4.5.0. .github/dependabot.yml sets a 5-day supply-chain cooldown whose stated purpose is dodging a package compromised shortly after publish; 4.5.0 is <48h old. Hard-pinning the SIGNING toolchain to an artifact fresher than the repo's own routine-update policy allows would invert that policy at the highest-privilege point in the pipeline. 4.4.0 has aged 23 days. Re-evaluate once 4.5.0 clears the window. 2. `packaging` is DERIVED from constraints.lock, not hardcoded. It IS a DEP-1 dependency (requirements.lock + constraints.lock both pin it at 26.2), so a literal would drift on the next Dependabot bump. Same run-time-read pattern as quality-advisory.yml's ruff pin, but FAIL-CLOSED instead of falling back to an unpinned fetch — a soft fallback on the release path defeats the pin exactly where it matters. Both installs also move ABOVE their `GITHUB_REF_TYPE = tag` guard so a workflow_dispatch dry-run exercises them; they were the only two of the five a dispatch could not reach, and an install that never runs before the tag cannot be validated before it. 3. ~=7.3.1 rather than ~=7.3 for cyclonedx-bom: the looser form floats the whole 7.x minor range, and a 7.4 could change the JSON shape scripts/security/sbom_finalize.py parses — which exits non-zero and FAILS the release. ~=7.3.1 still takes patch fixes. This is residual-risk reduction, NOT an alert closure: ADR 0034 §3 shows from this repo's own data that exactly-pinned tools (bandit==1.9.4, zizmor==1.5.2) are still flagged while --require-hashes installs are not. Pinning `sigstore` also pins the TOP only — its ~30 transitive deps still float at signing time. Closing it needs the hashed release-tools lock (ADR 0034 option B), which is an owner call and is deliberately NOT built here. GUARD: a pin nothing watches rots back to unpinned, and Dependabot has no updater for an inline `pip install X==Y` in a workflow (its `uv` ecosystem reads only pyproject.toml + uv.lock), so both a stale pin and a DELETED pin are invisible. tests/test_ci_venv_pinning.py gains two tests: a BLANKET scan of every pip-install target in release.yml (so a NEW unpinned install added tomorrow fails too — the case a fixed name list cannot see), plus a non-vacuity table asserting each tool is still installed and still pinned at EVERY occurrence. Guard proven by injecting one regression at a time rather than trusting a green run: un-pin sigstore -> 2 failed (blanket scan + the named backstop) DELETE the install -> 1 failed (only the backstop can see this, as designed) un-pin 1 of 2 `build` -> 3 failed (proves every occurrence is checked, not just the first) restored -> 12 passed Both workflows re-parsed as YAML. 192 passed across every test that reads .github/workflows. Line-neutral where ADR 0034 requires it: the scanner re-fires a dismissed alert under a NEW number when a line moves, so each pin was made in place. Not done: security.yml's `pip uv` (line 55) and `pip-audit` (81) stay unpinned — the DEP-1 job is contents: read and outside the release path.
… sweep recorded Follow-up to 09767bf. The sweep widened the "encrypted at rest" assertions correctly, but review found five things it got wrong or left half-done. 1. Five more bare `decrypt()` calls on cell-bound columns (the defect the sweep fixed once, in test_ack_sent_store). Each fails closed on an mfenc:v2 value and was only invisible because the v1 prefix assertion above it failed first: - test_store_encryption x3, queue.last_error (store.py binds cell_aad("queue","last_error", <row id>)) - test_sqlserver_store x1, response.body (sqlserver.py:4326) - test_postgres_store x1, response.body (postgres.py:3668) The SS/PG pair skip without a live DB, so they surface only in CI. Both now read destination_name back from the row rather than rebuilding the "\x1fack:" sentinel. 2. Seven new comments asserted a causal link that does not exist: "the at-rest format follows [store].aad_bind (v2 by default), so a v1-only prefix would fail here". aad_bind is read in exactly one place -- base.py:1736, make_cipher(..., write_v2=settings.aad_bind), reachable only via build_cipher/open_store. Every one of these tests builds its own cipher, and make_cipher's write_v2 parameter still defaults False, so they observe mfenc:v1: in a normal run and the setting has no influence on them. Reworded to the true reason: the marker version is the cipher's business, not the assertion's. 3. Two poison-injection comments named the wrong failure mechanism. Measured: 'mfenc:v1:not-base64-$$$' -> ValueError: Nonce must be between 8 and 128 bytes 'mfenc:not-base64-$$$' -> CipherError: unknown at-rest marker version `_parse` splits on the second colon, which is absent, so the blob is empty and b64decode("") succeeds -- the raise comes out of AESGCM, not the base64 path. The decision to keep these two at v1 was right; only the stated mechanism was wrong. 4. test_store_encryption's M9 section claimed "WRITES v1 BY DEFAULT" / "the shipping default never emits a v2 marker" in the same file where the sweep added six "v2 by default" comments. ADR 0148 falsified the prose, not the tests: they pin make_cipher's PARAMETER default (still False), while the shipped store writes v2 via build_cipher. Both defaults now named separately. 5. test_bytes_per_message_amplification documented the at-rest shape as mfenc:v1:<key_id>:<b64>. Receipt (real numbers, run from this worktree). Default mode over the 11 edited modules: 166 passed, 351 skipped. Forced-v2 leg (MEFOR_TEST_FORCE_AAD_BIND=1) over all 60 test modules that import store.crypto -- the scanned set, not a 12-file window: 10 failed, 1159 passed, 359 skipped, down from 13 failed. The 3 that stopped failing are exactly the last_error sites in (1). All 10 remaining are deliberate-v1 sites that MUST red under a forced v2 writer: the four CRYPTO-1 frozen-writer pins, test_active_marker_prefix_v1_and_v2, test_key_id_is_a_fingerprint_not_zero, and the four v1-premise dual-read tests. No messagefoundry/ change. ruff check + ruff format --check clean over tests/.
…inish the ADR 0034 rows Follow-up to 9bf7178. The pins were right; the guard protecting them was not, and two of the four ADR 0034 rows were only half-applied. THE GUARD ACCEPTED `>=` AS A PIN. `_VERSION_OPS` listed ("==", "~=", ">=", "<=", "!="), so `pip install "sigstore>=4.4.0"` — which resolves whatever PyPI serves at tag time, byte-for-byte the exposure ADR 0034:190 calls "the highest residual in the group … runs with the OIDC identity used to publish" — passed green under a test named test_release_toolchain_pin_is_present, and its own failure text never fired. Split into _PIN_OPS ("==", "~=") for the pin decision and _SPEC_OPS (every PEP 440 operator) for matching a token to a package NAME, so an unpinned `sigstore>=…` is reported as unpinned rather than as a missing step. Three more holes in the same scan: - `"/" not in target` exempted every URL and git+ target — the one class that cannot carry a pin at all. `pip install https://…/evil.whl` inserted before the Sigstore step scored ZERO failures. Remote is now tested FIRST and always reported; only local path shapes are exempt. - `line.split(" install ", 1)[1]` disagreed with the `\bpip\s+install\b` regex that selected the line, so `pip\tinstall` raised IndexError instead of asserting. Both now anchor on one regex, which also picks up `pip3 install` and `pip --quiet install`. - The vacuity floor said >= 6 while the file has 8 pip installs — two steps could be deleted before the scan noticed. Floor is now the real count. ADR 0034 ROWS FINISHED: - The harness `packaging` install got the pin but not the venv. ADR 0034:192 prescribes both, and the venv is the half carrying the risk: it landed in release-harness's MAIN interpreter, and the steps after it attach the wheel to the release and publish to PyPI. Now /tmp/harnesssmoke, mirroring the engine job's /tmp/relsmoke. - The two SBOM installs had drifted apart, which quietly retired ADR 0034:205's pre-tag validation route ("run security.yml's sbom job … the install command there is byte-identical"). Nothing in PR CI executes release.yml, so that dispatch was its only dry-run proxy. Both lines are now the same command and a test keeps them that way. The register itself said "Recommended hardening — identified, NOT done" over four rows this branch had already done, and called them "an owner decision, not a drive-by". Rewritten with per-row status and the reason the owner gate is retired for them (they are PR-visible now). What is NOT closed is stated plainly: these pin only the top of each install, sigstore's ~30 transitive deps still float, and §3's own data (bandit==1.9.4 pinned and still flagged) says no pin moves the Scorecard finding — option B remains the only thing that does, and remains an owner call. New guards: security.yml's unpinned installs are a REGISTERED set (pip/uv/pip-audit — contents:read, schedule-only, nothing anyone installs) so a new one there still reds; the SBOM twin-identity check; and a PR-time canary that constraints.lock still carries exactly one `packaging==` line, because release.yml derives that pin at run time and `exit 1`s without it — on the tag push. RECEIPTS. Guard mutation-tested against a TEMP COPY of the workflows (repo never modified), one regression at a time — control 0 failures, no crashes: sigstore == -> >= 2 blanket_scan + named_backstop build == -> >= (1 of the 2 installs) 2 blanket_scan + named_backstop[build] sigstore install deleted 2 blanket_scan + named_backstop new unpinned named install 1 blanket_scan new https://…whl install 1 blanket_scan (scored 0 before this commit) new git+ install 1 blanket_scan packaging pin -> bare 2 blanket_scan + named_backstop[packaging] TAB / pip3 / flag-before-subcommand 2 each (was an IndexError crash) SBOM twin drift 1 sbom_twin security.yml gains an unpinned target 1 security_registry --upgrade-deps / lost --require-hashes 2 / 1 constraints.lock loses packaging== 1 packaging_canary Both workflows re-parse as YAML; permissions, triggers and job lists are byte-identical to origin/main (checked, not assumed). 195 passed / 10 skipped across all 14 test modules that read .github/. No messagefoundry/ change.
wshallwshall
enabled auto-merge (squash)
July 29, 2026 17:16
The console publish job landed while this branch was open and installs pip, build and packaging unpinned. The guard added earlier on this branch rejects exactly that, so updating the branch against main turned all three test legs red -- the guard catching a real regression written after it was, which is the best evidence it works that this branch could have produced. pip and build take the same explicit pins the sibling build jobs use. packaging is derived from constraints.lock the way relsmoke and harnesssmoke already do, so one lock bump moves every release-path packaging install together instead of letting them drift apart. Also adds the check the pin guard structurally cannot make. Getting here, an edit put a literal 0x01 byte in release.yml -- a sed backreference written into a non-raw Python string -- and the file became unparseable YAML that GitHub Actions could never have run. The pin guard passed green through it, because it greps lines and never parses the document. So the file is now verified by parsing it, and its permissions blocks and triggers are compared against origin/main as parsed structures rather than eyeballed: jobs release, release-webconsole, release-harness; permissions identical; triggers identical. Verification: pin guard 15 passed; full suite 9331 passed, 818 skipped, 1 failed. That failure is tests/test_gate_installed_parity.py, which is PRE-EXISTING and unrelated -- it reproduces identically on clean main, this branch touches neither the gate source nor that test, and the test is LOCAL-MACHINE only (it skips on CI, where no installed gate exists). It reports that this workstation's installed ~/.claude/hooks copy is older than the source that PR #36 merged; re-running install-gate.ps1 is the fix, and that is deliberately an owner action because it activates the EnterWorktree rule for every session on the box.
wshallwshall
added a commit
that referenced
this pull request
Jul 29, 2026
main's #48 pinned the RELEASE path (sigstore, build, cyclonedx-bom, packaging, pip) and built a blanket-scan + registry to keep it that way. This branch pinned the BLOCKING SCANNERS in security.yml (pip-audit, uv, semgrep) and checksum-verified the gitleaks download. Same subject, opposite halves, so they conflicted in tests/test_ci_venv_pinning.py. Resolved toward main's machinery, which is the better base: its _PIP_INSTALL scan catches a NEW unpinned install anywhere on the release path, which a fixed job list cannot. My test_blocking_jobs_pin_the_tools_they_install is DROPPED rather than merged -- it duplicated that scan for three named jobs and would have been a second gate on the same property. Two substantive corrections to main's side, both load-bearing: 1. SECURITY_YML_ACCEPTED_UNPINNED registered `uv` and `pip-audit` as accepted unpinned, reasoning that security.yml's jobs are "schedule/dispatch-only" and "produce nothing anyone installs". The first half is factually wrong: that file triggers on `pull_request` and SEVEN of its jobs are required contexts, pip-audit among them. So those were unpinned dependency intake on every PR, inside the gate whose stated purpose is proving nothing unpinned enters the tree -- and `uv` is the resolver producing every lockfile that job then audits, so an implicit upgrade can change the exported set and red the `git diff --exit-code` for a reason unrelated to the change. Both are now pinned and out of the registry, which now holds only the `pip` bootstraps. The docstring records the correction rather than quietly rewriting the rationale. 2. RELEASE_PINNED_TOOLS gains the four security.yml scanners, so a pin that vanishes WITH its step is caught -- the direction the blanket scan structurally cannot see. test_release_asset_downloads_in_blocking_jobs_are_checksum_verified is kept: it is the non-pip half of the same intake and main has no equivalent. It excludes trivy (continue-on-error, so it cannot turn a required context green) and strips comments before matching, because the gitleaks step's rationale comment QUOTES the `curl | tar` it replaced -- a detector that would otherwise count itself. security.yml auto-merged clean: main touched only the SBOM step, this branch only the pip-audit/semgrep/gitleaks steps. Verified after resolution: 154 tests pass across every affected suite (including main's byte-identical-SBOM twin check and the release-pipeline structure pins), actionlint clean.
wshallwshall
added a commit
that referenced
this pull request
Jul 29, 2026
#57) release.yml's webconsole job carried python -m pip install --quiet "packaging==$PKG_PIN\" A stray backslash before the closing quote left the shell string unterminated, so that job would have died the first time anyone cut a webconsole tag. I introduced it in #48 while fixing a DIFFERENT escape bug: a sed backreference written into a non-raw Python string became chr(1), I switched to raw strings, and a raw string then preserved the \" I did not want. Two escape failures from opposite directions in one edit. Nothing could have caught it, which is the more interesting half: - the YAML parsed, because a broken shell string is still a valid YAML scalar; - the pin guard passed, because it greps for pin syntax and the line does contain packaging==; - no CI leg executes it -- the step is tag-only and webconsole-gated, and no pull request runs a tag-only release job. So the first execution would have been a release. A one-line fix leaves that hole open for the next edit, so this adds tests/test_workflow_shell_syntax.py: parse every workflow, extract every shell run: block, and ask bash -n whether it is syntactically valid. It resolves the shell the way Actions does (step > job defaults > workflow defaults), skips pwsh blocks and Windows runners with an unspecified shell, and substitutes ${{ ... }} first since Actions templating is not shell. 126 blocks across 12 workflows check clean. Two liveness tests sit in front of the check because a glob or extractor that silently found nothing would make it vacuous -- it asserts the block count instead of trusting a green. Proven to kill the real bug, mutation verified as APPLIED first: reintroducing the backslash exits 1, restoring exits 0. An unapplied mutation reads as a pass, so the injection is byte-checked before the result is believed. One implementation note worth keeping: bash -n must take a FILE, not stdin. A script containing a heredoc makes bash read the heredoc body from the same stream it is reading the script from, and it blocks for a terminator that never arrives -- a 30s timeout, not a syntax error. The timeout is caught and named per block rather than failing the run blind. Scope stated in the docstring: this checks syntax, not behaviour. It cannot catch a command that parses and then does the wrong thing.
wshallwshall
added a commit
that referenced
this pull request
Jul 29, 2026
…ect the provenance claims (#54) From an audit of this repo's controls against an external "reviewing AI-generated code" guide. Most of that guide's advice presupposes a second human reviewer; what survived translation to a solo, auto-merge, AI-authored repo is here. THE MERGE GATE IS NOW KNOWABLE, AND NOT NEUTERABLE IN ONE LINE. security.yml documented the downgrade verbatim: "To temporarily downgrade one, add `continue-on-error: true` back to its job." A job-level continue-on-error makes the job report SUCCESS, so the context branch protection requires stays green while the scanner's findings are discarded -- and with zero required approvals and auto-merge armed, the PR merges unread. Nothing guarded that. "Is this check blocking?" could not be answered from a clone either. Five in-repo statements disagreed with each other and with the server: docs/CI.md said 8, manifest-lint.yml said 4, docs/design/freethread.md said 7, tests/test_push_guard.py said 12. docs/CI.md also called zizmor "Blocking" (it is not), said Scorecard runs on PRs (no pull_request trigger), and named the CLA context "CLA Assistant" when the real string is `cla`. * .github/required-contexts.txt -- the checked-in claim, with the required-but-absent trap documented for anyone adding one. * tests/test_required_contexts.py -- reconciles every prose statement against it. * tests/test_security_posture.py -- refuses continue-on-error, `|| true`/--exit-zero, and a skippable job-level `if:` on any job backing a required context; pins the advisory jobs the OTHER way so an accidental promotion is equally deliberate. * actionlint -- pre-commit hook + a checksum-verified step in zizmor.yml, for the invalid-expression class that aborts workflow COMPILATION so required contexts silently never appear. docs/CI.md asked for actionlint; nothing ran it. A DEPENDENCY NAME PYPI CANNOT VOUCH FOR IS NOW REJECTED. pip-audit asks "does this pinned version have a known CVE". A freshly registered hallucinated name has no advisory, so it resolves through `uv lock`, hashes into requirements.lock and installs under --require-hashes: every DEP-1 control passes it. The only thing in the way was that `uv lock` cannot resolve a name PyPI does not serve -- a resolver side effect, not a control, and exactly what slopsquatting defeats. scripts/security/new_dependency_check.py sweeps every distribution in pyproject.toml, core and every extra, and fails on a name that does not exist, publishes no files, was first released inside a 90-day floor, or is served under another project's canonical name. Age comes from the EARLIEST release across all versions, so a squatter who registers then publishes later does not look established. It runs as a step in the already-REQUIRED pip-audit job, so it blocks today with no branch-protection change. Stdlib-only, and fails CLOSED: unreachable PyPI exits 2, and examining zero distributions exits 2 so a schema change cannot read as a clean sweep. Verified against live PyPI: 41 distributions clean, an injected `hl7-dicom-bridge>=1.0` rejected. Its blind spot is documented and pinned by a named test rather than glossed: it cannot tell a REAL package is the WRONG one -- measured, `py-webauthn` exists, publishes, is years old and is canonically named, yet is AS207960's project rather than the intended `webauthn`. THE PROVENANCE CLAIMS NOW MATCH REALITY. The standard prescribed a `Co-Authored-By` + `Tier:` trailer, listed it under "Built (in code today)", put it in the retained-evidence set, and cited it for two claims in the register aimed at "adopters and auditors". Measured: 0 trailers and 0 `Tier:` lines in 300 commits, while 81 tracked files instruct omitting it -- because cla.yml allowlists three identities, a trailer co-author reads as an unsigned contributor, and `cla` is required. The prescription and the prohibition were both in the tree, neither referencing the other. Fixed by making the documents honest rather than by building a hook. The claim "Provenance recorded at commit granularity by convention" is WITHDRAWN with a replacement wording, and the deviation corrected to the broader truth: no per-commit model/version record at any granularity. tests/test_ai_provenance_claims.py asserts CONSISTENCY rather than counting git log, because CI checks out shallow and "0 of 0 commits" is a gate measuring nothing. THE GATES' OWN TOOLCHAIN IS PINNED. The pip-audit job -- whose purpose is proving nothing unpinned enters the tree -- installed its auditor with a bare `pip install pip-audit`. DEP-1 bootstrapped `uv`, the resolver producing every lockfile it then audits, unpinned. semgrep rode `~=1.90`. gitleaks was `curl | tar` with no integrity check while the sbomqs step in the same file already verified a checksum. Now pinned (each to what the prior spec already resolved to, so no behaviour change) and checksum-verified. This also reconciles with #48's release-path pinning, which registered `uv` and `pip-audit` as accepted-unpinned on the reasoning that security.yml's jobs are "schedule/dispatch-only". That half was wrong: it triggers on pull_request and seven of its jobs are required contexts. VERIFICATION. Nothing was accepted for being green. 7/7 mutations into security.yml were caught by the intended assertion; reverting the doc fixes reproduced 5/8 required-context and 4/6 provenance failures; actionlint catches an invalid expression inside a comment -- the exact class docs/CI.md describes. Bugs that discipline found in this work: an invariant asserting 12 contexts == 12 jobs (they are 10 -- the test legs share a matrix job); `find("**Built")` slicing a section 400 lines off target so a guard checked nothing; a detector flagging `-r requirements.lock`, the most pinned install in the file; a detector matching `| tar` inside its own comment explaining the pipe it replaced; and an attempt to patch pre-commit's generated shim, which tests/test_ledger_check.py correctly refused -- `pre-commit install` rewrites that file, so the patch was futile as well as forbidden. Owner action still needed: branch protection is server-side and unchanged by this PR. required-contexts.txt deliberately mirrors the LIVE server, so backlog-hygiene is recorded as pending promotion rather than added -- adding it first would make the file lie in the one direction it exists to prevent.
wshallwshall
added a commit
that referenced
this pull request
Aug 4, 2026
…ones (#163) * docs(backlog): close BACKLOG #226 — the estate Hybrid-layout sweep is done, off-repo The per-feed Hybrid split (connections.toml / <INBOUND>_router.py / <INBOUND>_handler.py / _<feed>_transforms.py) landed across the ported estate in the maintainer-internal migration repository. Owner-attested; nothing in this repository changes, which is also why leaving the item open could never have closed it. Both "Also" clauses are recorded as NOT delivered, with the reason each is not a residual of this item: - "align the IDE Corepoint-import / scaffold path to emit the Hybrid layout" — there is no Corepoint-import path in ide/ to align. That tooling is #105, still open, so the clause is a constraint on #105's design rather than work #226 can perform. The scaffold half is misaddressed too: Insert Element (#48) drops per-file idioms into the current buffer (ide/src/insertElement.ts:1-5) and emits no multi-file feed layout. - "consider a recursive-glob / folder-per-feed loader enhancement" — filed as a consider, and not taken: load_config still globs *.py non-recursively (config/wiring.py:4162), the flat-merge behaviour the Hybrid layout is built around. Follows the #227 precedent: close the primary, state the off-repo/misaddressed residuals explicitly so the item is not re-opened for them. backlog_status_check.py: OK — 277 items, each declaring exactly one status. * fix(ledger): teach the number-space gates to span an archive, and fix two holes found proving it Prerequisite for moving the 185 closed BACKLOG items into docs/archive/backlog/. No item has moved yet; this only makes the guards able to see one when it does. The item namespace will span two paths, so every guard now reads their UNION: - backlog_status_check.py: scan() takes (label, text) pairs and parses them as ONE namespace. A number re-used across BACKLOG.md and the archive was structurally undetectable before -- `seen` was per-parse -- which is the erratum's own shape. - ledger_check.py: triggers on any backlog-bearing path, not the one literal, and builds head/base as the union. Reading the union on both sides also removes a false positive: the move relocates 185 items, so head-union == base-union and `head - base` stays empty, where a per-file view would report 185 vanished numbers with a remedy that renumbers cited items. - alloc.ps1: sweeps both paths in the all-refs term and the working-tree term. - backlog-hygiene.yml: accepts a banner updated in either location. Two pre-existing defects surfaced only because the gates were made to fail on purpose first, neither of which is about the archive: 1. alloc.ps1's working-tree term has NEVER worked. `[regex]'^...'` anchors at the start of the STRING; the term feeds it `Get-Content -Raw`, one string starting "# Backlog". Measured: 0 of 277 headings matched without Multiline, 277 with. The all-refs term hid it by covering every number committed somewhere -- i.e. every case except the uncommitted one this term exists for. 2. backlog-hygiene.yml diffed BASE_SHA..HEAD_SHA (two-dot), which credits a PR for main-side changes to paths it never touched. One main-side edit to BACKLOG.md -- the move being a large one -- would let every PR with an older base pass the "must update BACKLOG.md" required check while enforcing nothing. Now three-dot, matching ci.yml's form for the same question. Anti-narrowing, because a green gate over a shrunken corpus is the failure mode: - `--min-items N` fails when fewer items are found than required, and CI pins 277. Without it, 277 -> 92 fails nothing. - The scanned files are always printed with the count; a bare integer cannot distinguish "items closed" from "a file stopped being read". - A liveness receipt in the test suite asserts the same floor. - An explicitly-named --backlog path that does not exist is an error, not a skip. alloc.ps1 gains `-ShowFloor`: print the floor and the swept paths, allocate nothing. Allocation is a one-way door, so before this the only way to ask what the floor could see was to spend a number on the question -- which is how it ran a whole release reading two refs while its header promised all of them. Get-Floor takes -Peek so the inspection cannot advance the high-water ratchet; the first -ShowFloor run against a planted number moved this clone's watermark 316 -> 990 before that was fixed. Proofs run, each observed failing BEFORE the fix: - archive-only unallocated #1007 staged: old gate rc=0, new gate BLOCKED. - #990 planted in the archive: old sweep floor 353 (blind), new sweep 990. - cross-file duplicate #118: detected, naming the other file. - banner violations inside the archive only: detected. - --min-items over a narrowed corpus: rc=1 with the scanned-file list. - -ShowFloor twice against a plant: watermark unchanged at 316. ruff + mypy --strict clean; 43 gate tests pass. * docs(backlog): move the 185 closed items into docs/archive/backlog/BACKLOG-CLOSED.md docs/BACKLOG.md becomes the ~92 items someone can act on: 8,742 -> 3,648 lines. The closed items are not deleted, summarised, or rewritten -- they are relocated verbatim, so the file that gets opened, grepped and edited daily is the open set. MOVED, NOT REWRITTEN. Every relocated block is byte-identical to the one that left BACKLOG.md, headings included. Verified mechanically against a pre-move copy: - 277 items before = 92 after + 185 archived, no overlap, union identical - every OPEN block byte-identical to its source - every ARCHIVED block byte-identical to its source - all non-item prose in BACKLOG.md preserved verbatim Byte-identical headings are load-bearing, not tidiness: GitHub derives anchor slugs from heading text, so all 64 archived->archived cross-references keep resolving with no edit at all. That is the whole argument for one archive file rather than a split by status, year, or cluster -- #52 alone receives 99 of the 110 in-file anchors, and its citers span #65 to #184, so no cut isolates them. Cutting item blocks at the next '## ' heading of EITHER kind, not the next numbered item: 4 blocks in this file are followed by a section header, which a naive cut would have dragged into the archive along with the prose beneath it. Anchors, all 127 re-resolved against real headings after the edit: - 44 rewritten in BACKLOG.md -> archive/backlog/BACKLOG-CLOSED.md#<same-slug> - 1 rewritten in the archive -> ../../BACKLOG.md#<same-slug> (#226 -> #105) - 3 cross-file links repointed: AOAG-DEPLOYMENT.md (#100, #101), ADR 0026 (#30) - 64 archived->archived untouched, by design 13 anchors still do not resolve, and ALL 13 WERE ALREADY DEAD BEFORE THIS COMMIT -- confirmed by running the same check over the pre-move file, which returns the identical multiset (11 bare-number self-anchors: #40 x4, #323 x3, #28, #29, #329, #333; plus 2 links to #13 in COUNSEL-ENGAGEMENT-BRIEF.md, a number this sequence never had). They are left dead and documented in the archive header rather than repointed at a plausible neighbour: a citation resolving to the WRONG item is the erratum's failure mode, and unlike a dead link it looks like success. The archive carries its retirement banner inline rather than in a sibling README -- docs/archive/throughput/ needs a README because it indexes five documents; one file does not, and two documents that must agree is a drift surface. It states the rules that keep the namespace honest: never renumber, re-open by moving the block back (never by copying, which creates the cross-file duplicate the status check now fails), and add any future archive file to alloc.ps1's $backlogPaths AND backlog_status_check.py's DEFAULT_SOURCES in the same commit -- a file named in neither is policed by nothing. Gates verified post-move: - backlog_status_check.py --min-items 277: OK, 277 items, and it now PRINTS "scanned: docs/BACKLOG.md (92), docs/archive/backlog/BACKLOG-CLOSED.md (185)" - ledger_check.py on the staged move: rc=0 (relocation adds no numbers, because head-union == base-union -- the exact false positive the union view removes) - alloc.ps1 -ShowFloor: floor 353 across both paths, next 1000 - 43 gate tests pass Note the floor is unchanged at 353 because the highest item (#353) is open and stays in BACKLOG.md. The archive-sweep fix is therefore PROSPECTIVE, not a save: it starts mattering the first time a top-of-range item closes and moves. * docs(backlog): re-score all 92 open items on the ten-level scale (2026-08-03) Every open item now carries a current value x difficulty score. Before this, 23 had none at all and the other 69 were from the frozen 2026-07-10 pass, which predates the 2026-07-28 reconcile that closed 31 items -- and a stale score reads exactly like a fresh one. Method, unchanged from the pass it supersedes: scored from each item's own Scope / Why / Trigger / Nearest-existing-mechanism text rather than rescaled from the old number, then adversarially verified against the code -- a second reader per batch attacking build state first, then verdict/tier, then value and difficulty. 26 of 92 scores were overturned by that pass and carry the refuter's number. The banner is the live record and the table is a view of it; both are written here and a mechanical check confirms 92 banners and 92 rows agree on every triple. THE RATIONALE IS REPLACED, NOT JUST THE NUMBERS. Carrying an old justification under a new score is how a banner comes to argue against itself: - #114's surviving "clean workaround via the on-demand test probe" is a claim PR #162 explicitly retracted -- both destinations' test_connection CREATE the target dir, so the probe cannot answer the question the toggle asks. That is what lifts it off the parity-with-a-workaround band to 6/3. Its replacement rationale was ALSO stale (it described the silent-ignore #162 had just fixed) and is hand-corrected. - #105's "large greenfield 71-action mapper needing its own ADR" describes an importer that has since shipped under ADR 0086. Scheduling barely moved, which is the reassuring result: only TWO tiers changed -- #64 DEMAND-GATE -> P3 (an index over levers that live in #62/#63/#47/#34, so it ships nothing runnable of its own) and #105 P3 -> DEMAND-GATE. Neither contradicts an explicit demand-gate/on-trigger ruling in its own body; that was checked for all 51 items carrying a prior tier. Distribution is RECOMPUTED with the table rather than carried forward, and all four lines sum to 92. The superseded table keeps its own frozen lines and now says so. Tiers: P1 4, P2 19, P3 17, DEMAND-GATE 52 Quadrants: quick win 22, big bet 5, fill-in 56, money pit 9 The four P1s: #341 (9/3, a handler returning a tuple/set of Sends delivers nothing silently -- an accept-and-drop CLAUDE.md §12 forbids), #324 (7/2), #325 (6/2), #327 (6/2). NOT in this commit: 24 items were found to misdescribe their own build state -- prose asserting a gap that has since shipped, or citing messagefoundry/console/, a package retired with #103. Those are banner corrections and land separately; the scores here already price the remainder rather than the original scope. Two mechanical faults were caught by reading the output rather than trusting the run: the quadrant regex omitted the hyphen in "fill-in", so 57 of 69 items took the fallback branch and got a SECOND score inserted beside the first; and the synthesizer's own distribution lines did not follow from its own table (11 quadrant mismatches, 8 ordering violations, difficulty summing to 95 of 92). The script now refuses to write when any line carries two score spans or the scored count is not 92. backlog_status_check.py --min-items 277: OK, 277 items across both files. * docs(backlog): correct 10 items whose own prose misdescribed build state The 2026-08-03 re-score flagged 24 open items as misdescribing what the code does. Re-verified each against the tree as it stands -- after the archive move and after PR #162, both of which post-date the findings -- and 10 survived. The other 14 did not, and are recorded here rather than silently dropped: #84 #95 #99 #105 #114 #124 #125 #127 #133 #137 #167 #169 #214 #228 Most of those already carry an amendment that covers the stale sentence (#95, #99, #105, #114, #124, #125, #127, #133, #228), and stacking a second ruling saying the same thing is noise. The rest did not survive verification: the finding was itself wrong or overstated, and a wrong correction in a ledger is worse than a stale one. CORRECTIONS ARE ADDED AS DATED AMENDMENTS, NOT PROSE REWRITES. This file's convention is to leave the original claim standing and rule against it, so the record shows what was believed and what replaced it. Silently editing the stale sentence would destroy the evidence that makes the correction checkable. Applied to #62 #64 #131 #166 #179 #182 #237 #321 #329 #336. Representative: - #329 "Five MEFOR_ALLOW_INSECURE_TLS cells": the census is FOUR. #323 landed and routed transports/direct.py through the clamp; it now holds no call to the raw predicate at all (:63, :197, :215). - #321 "no test asserts the detectors can see a site code": false -- tests/test_scan_forbidden.py has per-class hit tests for at least the site code (:126), a customer name (:83), a case-sensitive code (:91) and a routable IP (:107). The detector-coverage half of its Proposed 2 is already in the tree. - #62 plans a dual-read over "existing mfenc:v1 rows", but cell-bound mfenc:v2 is the default writer (settings.py:383 -> base.py:1841; crypto.py:36), and v2 folds (table, column, pk) into the GCM tag -- so a body landing under a different column must be RE-ENCRYPTED, not merely re-encoded. That tightens the catch. - #64's ordered plan still reads live ("Nothing builds before it"), but the measure-first phase completed 2026-07-12 (ADR 0051) and its step-2 lever is refused outright (ADR 0055 withdrawn; ADR 0107 "Do not build F2 or F3"). The refuters removed two overclaims before they landed: #62's draft asserted a live store holds both mfenc markers (a fresh store under the shipped default holds only v2 -- the defensible claim is that a MIGRATION must expect both), and #64's asserted the multi-DB log split still remains, which could not be verified against ADR 0098 and would have been a fresh false claim. No item closes here: in every case the correction narrows the remainder rather than discharging it, and the 2026-08-03 scores already price the remainder. backlog_status_check.py --min-items 277: OK, 277 items, one status banner each. * docs(backlog): file BACKLOG #1000 — prove each required merge context can fail Escalated by the coordinator on the ground that it outlives the PR that fixed it. Deliberately NOT filed as "fix the two-dot diff": that instance already landed in 39b62bf, and filing shipped work is the rot the hygiene gate exists to prevent. The item is the CLASS. `.github/required-contexts.txt` names 13 contexts that block merge, and not one of them is proven able to go red. The deliverable is a negative control per context -- a fixture carrying the exact violation that context exists to catch -- plus a CI job that fails when a required context has none, so the coverage cannot silently decay as contexts are added. Scoped narrower than "test the gates" on purpose: it does not re-test what each gate checks, since the gates' own suites do that. It asserts one property per context -- this gate is capable of failing. The argument is that the class has now fired at least four times here, each found by hand and none by CI: #334 semgrep, required and blocking, scans a two-directory allow-list #327 six .gitignore rules are the sole control over maintainer-internal docs, and nothing asserts they still match anything #321 the forbidden-content gate exited 0 on a real site code and partner product #325 the same gate's home-path detector misses 1 of 4 spellings of a Windows path Each is correctly filed as its own defect. None of them establishes the property that would have caught all four before they shipped, and that property is a different artifact from any of the individual fixes. Value 7 / Difficulty 3, quick win, P1 -- not demand-gated; the trigger fired four times. Ranked table and all four distribution lines recomputed to 93 open items; a mechanical check confirms 93 banners and 93 rows agree on every triple. Number allocated atomically via scripts/coord/alloc.ps1 (#1000 -- the first in the post-partition public sequence, clamped to >= PUBLIC_BACKLOG_FLOOR), never grepped. backlog_status_check.py --min-items 277: OK, 278 items across both files. The floor is a floor, so growth passes it; it is there to catch shrinkage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the two deferred follow-ups from #37/#38.
1. The at-rest assertion sweep (41 sites)
aad_bindnow defaults on, so the default at-rest format ismfenc:v2— but 41 assertions checkedstartswith(PREFIX)(v1-only) to mean "is this ciphertext at rest". Each is nowMARKER_PREFIX, which matches both versions.The risk here was asymmetric, and it drove the whole approach: sweeping a site that deliberately pins v1 would silently delete CRYPTO-1 frozen-writer coverage. 13 sites were left as
PREFIXon purpose — the frozen-writer gate itself, three that pin the key_id's field position (widening makes the positive unsatisfiable and the negative unfailable), fivepremiseassertions whose dual-read proofs collapse into tautologies under v2, and two poison injections that need a version the cipher dispatches on. 0 sites were ambiguous.Three of the swept sites are negatives (
not startswith) — as v1-only they would have passed silently on a leaked or unmigrated v2 value. Widening those is strictly stronger.Vacuity proven first-hand, not asserted: patching
AesGcmCipher.encryptto identity kills 12/12 of the SQLite-runnable swept-site tests (control 12 passed → mutated 12 failed). Stated limitation: the migrating half can't be probed that way —_encrypt_existing_rowsdoesn't converge against a no-op writer, so those hang rather than fail.2. Release-toolchain pins — and a guard that could not fail
Pinned
pip==26.1.2,build==1.5.0,sigstore==4.4.0,cyclonedx-bom~=7.3.1,packaging==26.2; every version verified live against PyPI.sigstoreis pinned to 4.4.0, not the latest 4.5.0, deliberately. 4.5.0 is one day old against this repo's owndependabot.ymlcooldown.default-days: 5— pinning the signing toolchain fresher than policy allows would invert that policy at the highest-privilege point in the build. The comment also records that this pins the top only; ~30 transitive deps still float at signing time.The guard had a real hole.
_VERSION_OPSaccepted>=,<=,!=as pins — sosigstore>=4.4.0, which resolves whatever PyPI serves at tag time, passed green under a test literally namedtest_release_toolchain_pin_is_present. Split into_PIN_OPS = ("==", "~=")for the decision and_SPEC_OPSfor name-matching. A 13-case mutation matrix (run against a temp copy; the repo was never modified) now kills every injected regression — includingpip install https://…/evil.whl, which scored 0 before this commit.Verification
messagefoundry/untouched (0 files). ruff clean · full suite 9145 passed, 816 skipped, 0 failed · both workflows parse, and permissions/triggers/job lists compared againstorigin/mainas parsed structures rather than eyeballed — identical.