wave 3a: negative controls, claim-release history, leak-gate source reporting (4 items) - #313
Merged
Conversation
…BACKLOG #1208) A connector factory takes a credential PARAMETER and emits it under a different SETTING name; every redaction control operates on the setting name. Three measured instances of that one crossing have now shipped fixes -- private_key -> sign_private_key and private_key_password -> sign_private_key_password (#1106), and proxy -> proxy_url, which is harmless only because the URL rule happens to cover the destination. Nothing asserted the mapping itself. The guard injects a unique sentinel into ONE parameter at a time, calls the factory, and reads the destination off the emitted settings -- so no rename has to be taught to it. The real redactor is then asked about whatever key the sentinel was found under. A parameter that reaches no setting, or that cannot be built at all, is a failure with a stated home rather than a silent pass. Measured by reverting the shipped redaction to each pre-fix state, over 58 probed parameters: pre-#1106 reddens exactly the 2 renamed with_signing parameters and leaves 56 green; pre-#1207 reddens exactly the 10 URL-bearing ones and leaves 48 green; the shipped code reddens none, before and after. The red is specific, so the control locates the layer that does the work. It also covers surfaces the sibling outcome-level guard cannot reach: de-classifying intake_api_key, intake_api_key_next, credential_password, ws_password and client_key_password turns this file red on all five and test_connection_factory_redaction_domain.py red on none, because that file drops a connector's credential arguments when the connector refuses to be built with them. Test-only. messagefoundry/config/wiring.py is unchanged.
…hat fails without one (BACKLOG #1000) Thirteen contexts are the entire merge gate and not one of them was proven able to go red. The class has fired at least four times here with no CI signal, each found by hand. Nothing enumerated the required list and asserted a control per entry, so a context added to branch protection tomorrow started life unproven and nothing said so. tests/negative_controls.toml registers, per context, the violation a control plants, the pytest nodes that must fail without it, the shapes it deliberately does NOT break, and the nodes for that half. tests/test_negative_controls.py reconciles the registry against the LIVE required set read from .github/required-contexts.txt -- 13 contexts, 13 covered -- and fails when a context has no control, when a control names a context that is not required, when a node id resolves to no test, when the asymmetry half is empty, or when a `ci` control names a command no step invokes. The reconciliation runs inside the existing `test` legs rather than in a new workflow. That makes it blocking today through contexts that are already required, adds no new required context, and needs no branch-protection change. .github/required-contexts.txt is read-only here. tests/test_merge_gate_controls.py supplies the controls the contexts lacked. The backlog-hygiene one lifts the workflow's OWN shell and runs it against a synthetic repository shaped like the PR it polices: the shipped three-dot diff exits 1 and the pre-fix two-dot form exits 0 on the identical fixture, which reproduces the recorded defect rather than asserting it. The others cover the ci-gate roll-up's terminal states, whether a failing test can make the runner exit non-zero (proven again under a hostile child encoding, not merely a favourable one), the gitleaks allowlist as a neutering path, severity floors on bandit and npm audit, and the cla context string. Every control is asymmetric and each was watched fail. Deleting `cancelled` from ci.yml's roll-up condition reddened 2 of 4 and left 2 green; widening one .gitleaks.toml allowlist entry reddened 2 of 4; adding `-ll` to the real bandit invocation reddened 1 of 4. Dropping a registry entry, renaming a red node away, and emptying an asymmetry half each reddened the registry gate. Every mutated file was restored in the same run. Two rows added to test_ci_docs_only_detector so the required-contexts file and this registry classify as CODE: pytest is gated on `code == 'true'`, so a docs-only classification would skip this gate on exactly the PR shape it exists for.
…s green was a fact about PATH order (BACKLOG #1000)
Caught before integration by running the lane under a deliberately hostile ambient
environment rather than only the shell it was written in.
The control ran backlog-hygiene.yml's own script via shutil.which("bash") with an
ABSOLUTE Windows path for the script. Under a Git Bash parent that passed. From
PowerShell, where PATH resolves `bash` to C:\Windows\System32\bash.exe -- the WSL
launcher, which runs in a different filesystem namespace -- all six hygiene assertions
failed with exit 127 and the backslashes eaten from the path. The verdict was a fact
about which shell happened to be first on PATH.
Three changes, each asserted rather than described:
- bash is derived from `git` (Git for Windows ships bash beside it) and only then
falls back to PATH. git is already required by these tests, so it is the
deterministic anchor.
- the chosen candidate must READ A TOKEN this process just wrote before any verdict
from it is believed -- a live positive control for the namespace, not a pattern
match on the word "system32".
- the script is invoked by a RELATIVE path, so no namespace or backslash conversion is
involved at all, and 126/127 are now a hard failure rather than a gate verdict. A
broken invocation read as "the gate refused this PR" is the probe defect #1000 itself
records.
test_the_bash_namespace_probe_rejects_an_interpreter_that_cannot_see_the_fixture is the
negative control for the resolver: sys.executable is a real runnable program that is not
a shell, so the probe must refuse it while accepting the resolved bash in the same call.
Verified green under PYTHONIOENCODING=cp1252, PYTHONUTF8=0, LC_ALL=tr_TR.UTF-8, a global
core.autocrlf, and a PATH ordered so the WSL bash comes first -- and unchanged under the
ordinary Git Bash ambient.
…istory (BACKLOG #1068) `claim.ps1 -Release` was `Remove-Item` and nothing else, so a `-Force` takeover left no record of who released whose claim, when, or why. The escape hatch is NECESSARY and stays: a claim whose holder worktree is gone would otherwise be stuck forever, and the alternative people reach for is hand-deleting the file, which leaves less evidence still. The item is auditability, not prevention. Every release now appends one JSON line to <git-common-dir>/mefor-coord/claims/.history -- key, releasing worktree and branch, prior holder, its branch and note, when the claim was taken, and force true/false. RECORD FIRST, then remove, and refuse the release if the record cannot be written. Both orders can lie once and only one lie is recoverable: recording after the removal reproduces this defect exactly (a completed release with nothing left to write the record from), whereas recording first can at worst claim a release that then failed -- which a `release-failed` correction line handles. Refusing is safe because a release is always retryable; the claim simply stays where it was. JSON Lines, LF-only, written in a single Write to a handle opened FileMode::Append + FileShare::Read, so two worktrees releasing in the same instant cannot interleave a record. It sits inside the claims directory safely: every reader there keys on a file NAME (-List and prune-merged.ps1 glob *.json, claim_check.py opens <item>.json) and ConvertTo-KeyFile always appends ".json", so no key can fold onto ".history". tests/test_coord_claim_release_history.py was red first -- 8 of 10 against the unchanged script. The two that passed pre-fix are guards against a WRONG fix (a refused release and an unclaimed key must record nothing). Per BACKLOG #1000 it carries the negatives a "somebody released something" record would pass: a -Force takeover must name the prior holder and the releaser as different paths, and force must record false when not passed.
…BACKLOG #1080) `-Synthetic` printed "Installed the SYNTHETIC template" and, three lines later, "CONFIGURED with the real token set." Both were true whenever MEFOR_FORBIDDEN_TOKENS was set -- the script wrote the file, and the scanner loaded the environment's list, which wins over it -- but nothing said the environment had OVERRIDDEN what had just been written. Reproduced on a throwaway checkout before the change, in that exact shape. The verify step now prints a `token source:` line naming what the scanner actually loaded (MEFOR_FORBIDDEN_TOKENS -> <path>, that variable carrying the list inline, or scripts/security/scan-tokens.local.txt) and an OVERRIDDEN banner when an install ran and the environment won. An INLINE value is named but never PRINTED: a non-path value IS the token list, and echoing it would publish what the gate protects into whatever log was being captured. An explicitly EMPTY MEFOR_FORBIDDEN_TOKENS is named as the CAUSE of NOT CONFIGURED, because that state does not fall back to the file and the ordinary advice loops forever. A second reporting defect in the same file goes with it: the scanner's exit code was discarded, so a refusal came back out as CONFIGURED and exit 0 -- contradicting this file's own header promise to "exit non-zero if the sections are empty". Measured with an impossible MEFOR_MIN_DETECTORS floor: scanner exit 2, script exit 0. It now exits with the scanner's code under VERIFY FAILED, and deliberately does not reprint the scanner's output, because a hit line can quote matched content. scan_forbidden.py is unchanged. Precedence is defined by its _resolve_token_text and this script necessarily re-expresses it, so tests/test_setup_leak_gate_reports_source.py measures every branch against the scanner's own detector counts in the SAME run and asserts the two sources yield DIFFERENT counts -- a fixture that could not tell them apart would pass while measuring nothing. Red first, 7 of 8; the one green was the paired positive control. Its child environment is pinned explicitly (MEFOR_* and PYTHONIOENCODING/PYTHONUTF8 removed, python resolved through a shim), so the result depends on the code rather than the shell.
Coordinator integration commit for the ungated half of wave 3, and the single
point where the "a PR that implements BACKLOG #N must update BACKLOG.md" context
is satisfied for the train. Banner text is each lane's own, carried verbatim.
CLOSED: 1000 1208 1068 1080
Both lanes are TEST-ONLY or script-only; neither changes engine behaviour.
#1000 registers a negative control per required context and a gate that fails
when one is missing -- inside the ALREADY-REQUIRED test legs, so it blocks
today and adds NO new required context. Adding one is an owner decision
and was deliberately not taken. .github/required-contexts.txt was treated
as read-only, per its own header: branch protection changes first.
#1208 follows the VALUE across the factory rename boundary with a sentinel,
rather than adding the fourth name list the item forbids.
#1068 records every claim release, -Force included, in claims/.history. Its
motivating instance is real: a force-release earlier today was correct on
evidence and left no trace.
#1080 makes setup-leak-gate name the token source it actually loaded.
ONE FINDING FROM THIS WAVE IS WORTH MORE THAN THE FOUR ITEMS. W3-L5 caught a
defect in its OWN control before trusting it: the backlog-hygiene negative control
resolved bash from PATH, so its green was a fact about PATH order rather than
about the gate. That is the third instance tonight of one class -- a green that is
really a statement about the environment -- after the WSL-bash baseline and the
PYTHONIOENCODING child-encoding defect. A control that has never been red is a
claim; a control that is green for an environmental reason is worse, because it
looks like evidence.
Ledger, re-derived with parse_items: live 242, open 188, closed-in-live 54,
archive 236, namespace 478 conserved. All three ledger gates pass.
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.
Wave 3a -- a deliberately PARTIAL wave. Two lanes were finished and verified, so they land now rather
than waiting on the extra-large rule-3c lane still building.
BACKLOG #1000 #1208 #1068 #1080
Scope
Test-only and script-only. No engine behaviour changes: nothing under
messagefoundry/outside testsis touched.
Two things a reviewer might helpfully undo -- please do not
required test legs, so they block today without one. Adding a context is an owner decision and was
explicitly out of scope.
.github/required-contexts.txtwas treated as read-only per its own header:branch protection changes first, then the mirror, in that order -- never the mirror alone.
-Force. The item was about auditability, not prevention. The.historyline nowrecords the key, the releasing worktree, the prior holder, whether
-Forcewas used, and atimestamp. Removing the escape hatch would break the case it exists for.
The most useful change here is not one of the four items
#1000's lane found a defect in its own control before trusting it: the backlog-hygiene negativecontrol resolved
bashfromPATH, so its green was a statement about PATH order rather thanabout the gate. A control that has never been red is a claim; a control that is green for an
environmental reason is worse, because it looks like evidence.
Verification
merge-base --is-ancestorreturns true vacuously for a branch with no commits of its own, socontainment was checked alongside each lane's own commit count rather than on its own.
main, with the instrument checked first:merge-tree --write-treeagainst aknown-conflicting branch returns exit 1, so exit 0 here means clean rather than blind.
parse_itemsfrom this branch: live 242, open 188, closed-in-live 54,archive 236 -- namespace 478 conserved. No duplicates, no item declaring more than one status. All
four items confirmed flipped to closed.