Skip to content

wave 3a: negative controls, claim-release history, leak-gate source reporting (4 items) - #313

Merged
wshallwshall merged 8 commits into
mainfrom
w3a-integration
Aug 10, 2026
Merged

wave 3a: negative controls, claim-release history, leak-gate source reporting (4 items)#313
wshallwshall merged 8 commits into
mainfrom
w3a-integration

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

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 tests
is touched.

Two things a reviewer might helpfully undo -- please do not

  • #1000 deliberately adds NO new required context. The negative controls run inside the already
    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.txt was treated as read-only per its own header:
    branch protection changes first, then the mirror, in that order -- never the mirror alone.
  • #1068 retains -Force. The item was about auditability, not prevention. The .history line now
    records the key, the releasing worktree, the prior holder, whether -Force was used, and a
    timestamp. 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 negative
control resolved bash from PATH, so its green was a statement about PATH order rather than
about 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

  • Two lanes merged clean and file-disjoint; each genuinely contributes commits. Note that
    merge-base --is-ancestor returns true vacuously for a branch with no commits of its own, so
    containment was checked alongside each lane's own commit count rather than on its own.
  • Merges cleanly onto main, with the instrument checked first: merge-tree --write-tree against a
    known-conflicting branch returns exit 1, so exit 0 here means clean rather than blind.
  • Ledger re-derived with parse_items from 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.
  • Leak scan run with the detector set confirmed non-empty, exit 0 across all changed files.

…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.
@wshallwshall
wshallwshall merged commit ef4d250 into main Aug 10, 2026
34 checks passed
@wshallwshall
wshallwshall deleted the w3a-integration branch August 10, 2026 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant