tls!: the engine always serves TLS, minting a self-signed certificate on first run (BACKLOG #1276 part A, ADR 0172 supersedes ADR 0143) - #534
Open
wshallwshall wants to merge 48 commits into
Conversation
…he caller's spelling (BACKLOG #1268)
`users.username` was the one identifier column in the SQL Server schema carrying no COLLATE
clause, so it inherited the DATABASE default -- case-INsensitive on a stock install -- while
every sibling identifier column in the same file pinned Latin1_General_100_BIN2 and both other
backends were case-SENSITIVE. `Admin` and `admin` were two accounts on two backends and one
account on the third, under a UNIQUE constraint that reads as if it had settled the question.
That portability defect became a security defect because a second site answered the same
question by a different rule. `_login_local` gated WP-3 bootstrap expiry/supersession
enforcement on a PYTHON comparison against the caller's input, while the lookup one line below
was resolved by the COLUMN'S collation. The two disagree in exactly one direction: `Admin`
fails the Python guard, so retirement never runs, then succeeds at the lookup and returns the
very row the skipped call would have disabled.
MEASURED before the fix, on a lapsed unclaimed bootstrap with the ASVS 6.4.1 credential expiry
disarmed so it could not mask the result:
login("admin") -> refused, account disabled -- the control fired
login("Admin") -> ok=True, session issued -- the control never ran
A lapsed first-run credential logging in because one letter was capitalised. SDS-3.7 exactly: a
compensating control resting on the false premise that the username the gate compared is the
username the store matched. No live exposure -- zero deployments (CLAUDE.md section 0).
Two limbs, and the second does not depend on the first:
- the column now pins the collation its own file's convention already required;
- the gate compares the value THE STORE RETURNED, then re-reads by id because retirement may
have disabled the row. That stays correct under a collation the engine does not control --
an operator-supplied database, a restored dump, a column altered downstream -- where the
column fix alone leaves the gate one ALTER COLUMN from being wrong again with nothing
reporting it.
Cost is one extra lookup on the bootstrap path only; an ordinary login does the single lookup
it always did plus a string compare, so the original guard's stated intent is preserved.
The test file keeps its own retraction: both gate tests PASSED against the unfixed code in
their first form, because they used the supersession arm and `create_local_user` retires the
bootstrap eagerly at service.py:2685 -- the account was already disabled before the login ran.
Supersession can never exercise this defect; only the expiry arm reaches the login path with
retirement still pending.
…entity decision may depend on store collation (BACKLOG #1268)
Records the decision behind the #1268 fix, because the diff shows WHAT and not WHY, and the
why is the half that decays. Two rules:
1. Usernames are case-sensitive, on every backend.
2. No identity decision may be delegated to store collation -- compare the value the store
RETURNED, never the caller's input.
Rule 2 is the load-bearing one and is deliberately independent of rule 1, so the gate stays
correct on a database whose collation the engine does not control.
Rejected alternatives are recorded with their reasons, because each is re-proposable and each
is wrong non-obviously. Case-INsensitive normalisation is the one a reader will reach for
first: it requires a canonicalisation that is not locale-neutral (the Turkish dotless i), so a
wrong fold silently MERGES two accounts -- and a UNIQUE constraint would enforce that merge
rather than catch it. It would also have to hold across three backends and the audit trail,
every one a fresh place for the two rules to diverge again.
Flagged and deliberately NOT decided here: two accounts differing only in case are themselves
a confusability risk, and case-sensitivity preserves it. That is closeable additively by a
registration-time refusal, which is a different control and does not require reopening this
decision. Not built, and not filed as an item by this lane.
Also records what the change does NOT do: the DDL is creation-guarded, so an existing SQL
Server database keeps its original column collation and no re-type is attempted. Zero
deployments means nothing to migrate; it is written down so a later reader does not mistake
the schema-hash bump for a column alteration.
…ort it instead of raising (BACKLOG #1259) The parse_items conflict refusal shipped and is on main. It protected programmatic readers and the CI leg. NOTHING called it before a commit, so the item's own failure sentence was still true: a conflicted docs/BACKLOG.md can be COMMITTED and every ledger gate passes over it. MEASURED at dd655da against a docs/BACKLOG.md carrying a realistic PROSE-level conflict, with the file restored byte-identical afterwards: ledger gate ....... Passed backlog_status_check.py ... exit 1 forbidden-content . Passed control characters Passed gitleaks .......... Passed overall rc 0 -- the commit lands bandit ............ Skipped THE FIRST VERSION OF THAT MEASUREMENT WAS CONFOUNDED AND SAID THE OPPOSITE. Injecting a conflict that ADDS headings made the ledger gate fail -- on OWNERSHIP, because both sides' numbers read as unallocated, not on the conflict. That reads as "already covered". A real ledger conflict is usually two sides editing PROSE, adds no heading, and is the case nothing sees. Testing the heading case instead would have closed the item as a non-defect. Two parts: 1. main() catches the ValueError per source and reports it with the PATH, returning 1. It raised from inside scan(), so every caller rendered an uncaught traceback -- which reads as "the checker is broken" rather than "your ledger is conflicted", sending an author to the wrong file, and the exception carries a LINE number but no path while the checker scans the published ledger plus every archive by default. Also removes a real double-parse. 2. A LOCAL pre-commit hook, scoped to the ledger files. NOT pre-commit/pre-commit-hooks' merge-conflict check, and the reason is not dependency squeamishness -- that repo is absent here, so it would be a new third-party source with its own pinned rev, but the deciding argument is SINGLE SOURCE. parse_items DEFINES what a readable ledger is, and CLAUDE.md section 11 requires this file be read through it and never a hand-rolled scan. A generic textual matcher is exactly that second, silently different definition. Scoped to the ledger files rather than always_run: the checker resolves its own sources, so running it elsewhere buys nothing and spends the credibility a gate needs on the day it fires. pass_filenames is false because feeding it a staged path list would narrow the corpus --min-items exists to protect. Verified both directions: the conflicted ledger now Fails with the checker's own message naming the file, and the clean ledger still Passes -- a gate that fires on the healthy case is one everybody learns to skip. Mutation-verified: repointing the hook entry elsewhere reds the wiring test. The wiring test asserts on DIRECTIVES with comments stripped, not raw text. Its first draft failed on this commit's own explanatory prose, which names the alternative it rejects -- a substring check over a commented config cannot tell a wiring from a note explaining why that wiring was not chosen. NOT DONE: the item's stale DO-NOT-BUILD banner still stands. A builder may not edit the ledger; it routes to whoever writes banners.
…-authored banner) ADR 0165 pairing. The fix and ADR 0169 are the builder lane's work; this banner is the lander's, because a builder may not author ledger content and a PR that honestly cites its item is red by construction until someone who may write docs/BACKLOG.md does. Closes 1268 with both limbs recorded, and states the residual rather than only the fix: the users DDL is creation-guarded, so an existing SQL Server database keeps its original column collation. Nothing to migrate -- zero deployments, CLAUDE.md section 0 -- and limb 2 is correct on such a database anyway, which is why it was built not to depend on limb 1. parse_items before/after: 313/232/81 -> 313/231/82, the expected 0 items / -1 open / +1 closed for one closure. Item 1268 declares exactly one status; the open-alphabet glyph was REMOVED from the original filing line rather than left to coexist with the closing banner. Built with plumbing (read-tree, update-index, commit-tree) so no working tree was touched -- the branch is checked out in a live sibling worktree. Plumbing bypasses pre-commit, so the ledger, forbidden-content and backlog-status checks were run by hand against the result.
…2's fifth sighting against its own argument Two body amendments to landed items. No item filed, no item closed, no banner status changed: parse_items reads 313 items / 232 open / 81 closed before and after, the expected 0/0/0 delta for an amendment. #1259 -- the 2026-08-15 banner had INVERTED. It read "DO NOT BUILD -- ALREADY BUILT AND UNLANDED ON PR #394" and named #394 landing as its own expiry condition. #394 landed, so the banner then said do-not-build over work that was only half done. That is the dangerous direction: it removes an item from the queue so nobody looks again. The first scope option shipped -- parse_items refuses a source carrying conflict markers, defined :94, called :132, reached before any parsing. The second did not, and the item's own failure sentence stayed true: no pre-commit hook called parse_items, so a conflicted ledger could still be committed with every gate passing. Also folded in a measurement the item did not carry, and it is the difference between a defect and a non-defect here: the SHAPE of the conflict decides whether anything catches it. A conflict that ADDS HEADINGS is caught incidentally, by the ownership guard, because both sides' numbers read as unallocated -- which renders as "already covered". A real ledger conflict is two sides editing PROSE, adds no heading, and the ownership guard has nothing to fire on. Measured with a prose-only conflict at dd655da, file restored byte-identical: every gate passes, overall rc 0, the conflicted ledger commits, while backlog_status_check.py on the same file exits 1. #1292 -- a fifth sighting on main's own tip, recorded because it WEAKENS the argument the fourth entry made rather than confirming it. The ack series is now 13, 14, 24 against a fixed sent=36. The fourth entry's case was that the shortfall reproduces to within one ack, which is what made "systematic" persuasive; 24 is a spread, not a reproduction. The verdict is NOT flipped to starvation -- two clustered points and one outlier is not a distribution. What changed is that neither reading now has an argument from reproducibility, so the store-side discriminator is the only thing that settles it. The same entry records that this sighting is NOT what was redding main. That run's failing assertion is tests/test_connscale_smoke.py:128, the FD probe. no_loss passed, and it is computed as agg_sent minus engine_read so acked is not in it at all. The sent/acked figures are real and incidental -- pytest prints the record's full repr on any assertion failure, and three sessions attributed the red to this item because the numbers pattern-matched it. When a sighting names an item, quote the line with the caret, never the values in the dump. Amendments to items already on origin/main add no heading, so the ledger gate's ownership check is never consulted; docs-only, so the claim gate's code-touching scope does not apply. Every added line verified cp1252-encodable. Cherry-picked by the lander onto PR #458 under ADR 0165: the dispatcher seat authored these dispositions and holds no push route, so routing them separately would have left #1259's INVERTED do-not-build banner standing over work landing in this very PR -- the #60 failure the hygiene gate exists to prevent. Applied with plumbing; merge-tree with a COMMIT-scoped base returned rc=0. Correcting one clause of the handoff that offered this commit: it said the change 'touches no file your branches touch'. It does -- all three commits touch docs/BACKLOG.md, the single-writer file. The conclusion (it cannot conflict) is right and measured; the stated reason is not. The regions are disjoint by ITEM, not by file.
…llision PR #428 landed as 4c28bad adding ADR 0167's index row; this branch adds 0169's. Both are appended as the LAST line of docs/adr/README.md, so git sees one line changed two ways. Every other path content-merged cleanly, including docs/BACKLOG.md and auth/service.py, which are changed on BOTH sides. RESOLVED AGAINST A COMPUTED TARGET, NOT BY EYE, because an index is exactly where keep-both-sides is a hope rather than a resolution: A = rows at merge-base 157 B = rows at PR head 158 (adds 0169) C = rows at origin/main 158 (adds 0167) target = C + (B - A) 159 result 159 rows, 0167 present, 0169 present, ZERO duplicate numbers Built with plumbing; no working tree touched.
# Conflicts: # docs/adr/README.md
…its EXIT CODE asks (BACKLOG #1235) `is_live_shape` was extracted so the exit list and the test stop restating the rule. The branch that NARRATES the verdict to a human was left behind, still testing `hit.number <= floor` alone and never consulting `pr_shaped`. MEASURED at 4c28bad, after the single-definition refactor landed: six hits printed BOTH [PR/issue/foreign-repo shaped -- very likely NOT a backlog citation] -> ABOVE THE FLOOR (1295) ... This is the live shape. two contradictory annotations on the SAME hit, two lines apart. All six are genuinely foreign (a code-server discussion, a Mirth discussion, a pyodbc issue). The exit code passes over them correctly; only the prose called them live. THE PART WORTH KEEPING: this survived the very refactor whose subject is single-definition cleanup. The definitions a tool COMPUTES with got unified; the one it NARRATES with was missed, because no assertion reads it. A third definition is not caught by the pass that removes the second. The test asserts the CONTRADICTION rather than either sentence alone -- in isolation each is true, since the hit IS above the floor and IS pr-shaped. Only their co-occurrence on one hit is the defect, so only that can pin it. Paired with a negative control: a genuinely live citation must still be narrated as the live shape and must still fail the gate, or the fix trades a wrong answer for no answer. Mutation-verified: restoring the floor-only branch reds exactly one test, and the suite returns to green on revert.
…t resolutions of one conflict Both this seat and the lane resolved the SAME docs/adr/README.md append collision against 4c28bad independently, neither knowing the other was on it. That is a coordination failure and it is mine: I pushed to a branch a live session was holding, having told that session the freeze SHA one message earlier. The lane's branch carries one thing origin does not -- BACKLOG #1235's annotation fix, making the citation gate's ANNOTATION ask the same predicate its EXIT CODE asks. Origin carries three the lane does not: #1268's closing banner and the dispatcher seat's #1259 / #1292 amendments. Neither side is discardable, so this merges rather than picks. merge-tree rc=0 -- no conflict. Verified on the result rather than assumed: - docs/adr/README.md: 159 rows, 0167 AND 0169 both present, zero duplicate numbers - docs/BACKLOG.md blob differs from BOTH parents, which is what a correct three-way merge looks like and is the check that catches a silent revert - zero conflict markers in any file
…ind fail-opens (BACKLOG #1229 residual)
The gate blanks quoted spans before any rule reads the line. It already learned once that two
ordered regexes cannot express "whichever quote opened first owns the span" -- that was the
straddle #1229 fixed. Two more places were still deciding span boundaries without that rule.
ONE: the scan resolved OWNERSHIP correctly and ignored ESCAPING entirely. In sh a backslashed
quote is an ordinary literal and the command around it RUNS, but the scan read it as an opener,
paired two of them across a live command and deleted the middle. Nothing reached any rule.
echo \" ; git -C <governed> checkout main ; echo \" ALLOW
echo \" ; git -C <governed> reset --hard ; echo \" ALLOW (destructive)
RULE-AGNOSTIC, and that is the reason it matters more than a missing case: it is a SCANNER
defect, so it disarms whatever rule sits behind it rather than one verb.
TWO: the quoted-program-path collapse was two ordered regexes run BEFORE the scan, double quotes
first -- the exact shape the scan replaced. It could pair a quote with a distant path token
ACROSS a gated command and rewrite the whole middle to a bare token, verb and arguments gone.
Both are now decided inside the single left-to-right pass, on spans it already owns. The
asymmetry is deliberate and is shell semantics, not an optimisation: a backslash escapes inside
a double-quoted span but NOT inside a single-quoted one, where sh gives it no special meaning.
Treating them alike would swallow the rest of a line from a trailing backslash, which is the
fail-open direction this function exists to avoid.
A THIRD GAP FOUND WHILE TESTING, WITH NO ESCAPE INVOLVED. PowerShell -replace is
case-insensitive by default, so the old collapse accepted every spelling by accident while the
rules downstream compare case-sensitively -- the token survived in a form they skip:
"...\git.exe" -C <governed> reset --hard DENY
"...\GIT.EXE" -C <governed> reset --hard ALLOW
Now canonicalised on emit, which is the fail-closed direction: it hands the rules a verb to
judge rather than a token they ignore.
ALL PRE-EXISTING, none a regression, and all three survived a dedicated pass over this exact
function. Nothing in the repository could see them: across the 13 existing gate suites, ZERO
carried a backslash-escaped-quote case (positive control: the token appears in four of them,
every hit unrelated).
The new suite carries both arms -- the escape must not hide a command, and a genuine quoted span
must still be blanked so a commit message cannot supply a verb. Without the second, "never blank
anything" would pass. Mutation-verified per arm with an applied-check on each mutant, because a
mutation that silently fails to apply reads exactly like a test that cannot fail: neutering the
escape arm reds the escape case, neutering the collapse reds the suite, dropping the
canonicalisation reds the two case-varied spellings, and the file restores byte-identical.
Verification scope: 581 passed / 13 skipped across every test_worktree_gate* module, ruff and
ruff format clean, mypy strict clean over 267 files. The x12 and xml extras are absent from this
venv, so this does not reproduce CI collection.
A case for `worktree add` was written and REMOVED rather than fixed around. A report said the
escape hid it; the discriminating probe -- same command with and without the escape -- allows
either way, so the escape hides nothing there and the rule-3b resolver says why. Keeping a
failing case built on a false premise would have pressured the next reader to widen a security
gate until it went green.
…ver it The lane handed me tip b05b48f (7 ahead of main at 13dd27d, by its own measure). Origin's head was 5627d4a, and b05b48f is NOT a descendant of it: the two lines had diverged by 4 commits on origin against 5 on the lane. A force-push of the handed-off tip would have discarded all four, which is why this is a merge: 5627d4a merge of the lane's 22ff4bb into the PR head c6070a4 merge origin/main -- ADR index append collision, already resolved a663b89 backlog: retire 1259's inverted banner, record 1292's fifth sighting 92d9002 backlog: close 1268, both limbs (lander-authored banner) Verified before committing, because a clean merge is not the same claim as a correct one: the merged tree carries fb1f53c's two files byte-identically to the fix (scripts/hooks/worktree_gate.ps1 and the escaped-quote test), so combining the sides did not restore what the fix removes. Note for the lane: the ADR index did not collide on your merge because c6070a4 had already resolved it on origin, not because this wave stopped colliding.
…d on a clean run (BACKLOG #1235)
Two more ways this gate said "clean" without that meaning anything.
ONE: THE CLI FALSE-CLEANED ON AN EMPTY POPULATION. The default path list is CWD-relative
(`Path("docs").rglob(...)`), not repo-relative. Measured from a directory with no docs/:
No unresolved backlog citation in 0 file(s).
Resolved against 0 allocated item numbers (open and closed).
exit 0
Both counts zero and nothing objecting -- a green that is a statement about the ENVIRONMENT
rather than the subject.
AND IT ARMS BY CLOSING THE ITEM, which is why it is not a hypothetical. The >200-file population
floor lives only in test_the_docs_scan_actually_covers_something, so it guards the pytest arm and
nothing else. Wiring this CLI into .github/ or .pre-commit-config.yaml is the documented way to
satisfy the item's first disjunct -- and doing so inherits a gate that reports clean when it
scanned nothing.
Now refuses non-zero on an empty path list OR an empty allocation, and prints what it scanned and
the directory it scanned from, because the cause is a relative default and the count alone does
not show that. Refusing on an empty allocation too is deliberate: an unreadable ledger resolves
every citation to "no filed item", which fails the other way and would bury a real run in false
positives. Both zeros mean the same thing -- the tool is not looking at this repository.
TWO: THE COVERAGE BOUND WAS ABSENT FROM EXACTLY THE RUN A READER ACTS ON. "Not scanned: the
private companion repository" printed only after the hits loop, so the early clean return skipped
it -- the bound missing at the moment someone concludes "clean", which is the whole population it
exists to qualify. It is in the module docstring, and a docstring is not what a CI log shows.
Now one named constant printed on every exit path. That is this item's own single-definition rule
applied to prose: two copies of a caveat drift, and the copy that goes stale is the one nobody
reads, because it only prints on the path they are not on. A test pins the single definition.
Both arms carry their control -- a populated scan must still reach a verdict rather than trip the
emptiness guard, or the fix is "always refuse"; and the clean path must be the clean path, or the
bound assertion proves nothing. Mutation-verified with an applied-check on each mutant, since a
mutation that silently fails to apply reads exactly like a test that cannot fail: disabling the
emptiness guard reds one test, removing the clean-run bound reds one test, and the file restores
byte-identical.
Verification scope: 45 passed in tests/test_dangling_citation_check.py, ruff and ruff format
clean, mypy strict clean over 267 files. The tooling tier was still running when this was
committed and is NOT claimed green here. x12 and xml extras are absent from this venv, so none of
this reproduces CI collection.
…ift guard demanded CI red on 1a7a31c: test_every_non_engine_test_is_classified failed with assert not ['test_worktree_gate_escaped_quote.py'] The new test imports no engine module, so the partition guard requires it to be named either in tests/tooling_manifest.txt or in _STAYS_WITHOUT_IMPORTING. That guard exists precisely so a new worktree-gate test cannot quietly join the engine legs and grow the tier back, which is what would have happened here. The manifest is the right side and the choice is not a judgement call: all thirteen sibling test_worktree_gate_*.py files are listed there and none appear in _STAYS_WITHOUT_IMPORTING. Placed between _emitter and _git to match the block's order. Verified before committing: the named file exists at this commit, so the companion check that every manifest name resolves to a real file stays satisfied, and the name appears exactly once so the duplicate check does too. Lander-authored on the builder-1 lane's behalf -- the fix is a mechanical registration fully determined by the guard's own message and by where its siblings already sit.
One genuinely new commit, 1972575. The lane's previous tip b05b48f is already fully merged here (measured: zero commits outstanding on that side), so this brings only the #1235 work and not a re-merge of what landed earlier. Merged rather than taking the handed-off tip, for the same reason as last time: origin carried six commits the lane's tip did not, including the escaped-quote manifest registration that cleared this PR's CI red. Verified in the result -- the merged tree keeps tests/tooling_manifest.txt at 122 lines with the escaped_quote entry present exactly once, and carries the lane's two files. CARRIED FORWARD FROM THE LANE, UNVERIFIED AND LABELLED AS SUCH: the tooling tier was still running when this was committed and its result is not in hand. That tier is where the changed gate actually runs, so it is the relevant regression check. What is verified is 45 passed in tests/test_dangling_citation_check.py, both arms mutation-verified with a byte-identical restore, ruff and ruff format clean, mypy strict clean over 267 files. That is not a green tier and is not offered as one. BEHAVIOUR CHANGE, deliberate: the CLI now exits non-zero where it previously exited 0 on an empty population. Anything invoking it from a directory without docs/ will now red where it passed silently. That is the fix, not a regression.
…LOG #1229 residual) The comment above the parametrisation said the verb set covers more than one rule, because the defect is rule-agnostic. IT DOES NOT. Measured, which is the only reason I know: both verbs come back with the SAME rule's message -- "would change the working tree of the SHARED PRIMARY checkout". Two verbs, one rule. That matters because the property the suite claims to pin is precisely the one it cannot see. A scanner defect disarms every rule equally, so coverage that would actually pin it has to exercise DIFFERENT rules; exercising one rule twice tests the parametrisation, not the property. And "denied" is a weaker claim than "denied by the rule we think". A suite asserting only that a deny happened cannot distinguish a rule-agnostic scanner fix from a lucky overlap in one rule's matching. The stronger form -- reading back the rule ids the gate RECORDS and asserting they are distinct -- was built independently on another lane, cross-checked at 10 of 11 rows passing against this gate unchanged, and that is the right home for the coverage. This file should not grow a second, thinner version of it. Corrected in place rather than deleted. A reader who saw only a fixed comment could not tell this suite had ever asserted coverage it does not have, and the gap is real. NOT fixing the coverage here, deliberately: widening this file would duplicate work that exists and is better, and the decision about which suite lands is the Dispatcher's and the Lander's.
… not this one (BACKLOG #1235) This file already opens by distinguishing itself from `backlog_citation_check.py` on SEMANTICS -- that gate asks whether a citation names the right ledger FILE, this one asks whether the number names anything at all. The names differ by one word and the paragraph exists because of it. They also differ on WIRING, and that is the half that misleads. The sibling is run by backlog-hygiene.yml and its test is named in ci.yml's docs-lane list. THIS script is run by no workflow and no pre-commit hook; it reaches CI only through its test, which is manifest-classified and therefore runs on the tooling leg. MEASURED WITH A POSITIVE CONTROL IN THE SAME PASS, because a zero without one proves nothing: `dangling_citation_check` returns ZERO hits across .github/ and .pre-commit-config.yaml, while `backlog_status_check`, `ledger_check` and `scan_forbidden` return 3, 2 and 3. The probe finds wiring where wiring exists, so the zero is a real zero. WHY IT IS WORTH WRITING DOWN RATHER THAN LEAVING TO BE LOOKED UP: a grep for `citation_check` HITS A WORKFLOW, and the hit belongs to the sibling. The answer is confident, well-formed, and wrong in the direction of believing a change here is CI-covered when it is not -- which is the direction that lets a behaviour change ship unexercised. I asked exactly this question about my own emptiness-guard change one commit ago and reported the answer as ONE PROBE rather than a clearance, because I had grepped the NAME while the gate is wired by manifest PATH. The gate-fix seat ran the path angle independently, with the control above, and cleared it. The clearance is theirs; this paragraph is so the next person does not have to spend the same round trip -- or, worse, gets the sibling's hit and stops. Comment-only. No behaviour change.
TEST-ONLY. The fix this rests on is already on this branch; nothing here changes the hook. The escape-blind span scan blanks text BEFORE any rule is dispatched, so it disarms whichever rule would have judged that text. The suite shipped with the fix parametrises two verbs and both reach the SAME rule, the one guarding the primary working tree. A rule-agnostic defect with single-rule coverage rebuilds the condition that let this class live in the first place: fourteen green gate suites, none able to see it. A later change repairing one rule's own matching while leaving the scan blind would keep every existing row green. This adds one arm per rule -- primary working tree, linked worktree hijack, shared git configuration, worktree removal -- against a real repository, because three of the four ask git itself what it is looking at and are unreachable from a synthetic fixture. IT ASSERTS THE RULE IDS THE GATE ITSELF RECORDED, not merely that a deny happened. Those are different claims, and only the second catches a fix that denies for an accidental reason; a suite checking outcomes alone passes when the right answer arrives by the wrong route. The ids come from the gate's own receipt log rather than from the deny prose, which is written for a human and is rewritten whenever a remediation changes. That instrument carries its own positive control, because a reader whose pattern has drifted returns a partial set and can satisfy the assertion by accident. Every offender row runs its bare, unescaped command first and requires it to be gated. An arm that cannot fail is not evidence, and that is not hypothetical here: a synthetic fixture reported an ungoverned verb as a fail-open when it was simply unreachable. Measured: five rows red against the pre-fix hook and green against the fix on this branch, while the remaining rows stay green against both. ONE ROW FROM THE ORIGINAL DRAFT WAS DROPPED RATHER THAN PORTED, said here so a deletion does not read as coverage quietly reduced. It pinned a different design for the same function -- refusing to let an escape extend a double-quoted span -- and this branch deliberately honours the escape there, which is what a shell does. That row asserted a deny no shell requires, so it is a statement about the rejected design and not about the gate. The file is named for what it adds rather than for the defect. The obvious name sat one character from the suite already beside the fix, which is a trap in a directory a reader scans by eye. Classifies itself in tests/tooling_manifest.txt. Without an entry test_every_non_engine_test_is_classified fails, and the classification is what decides which CI leg a suite runs on -- an unclassified tooling suite can be deselected on the engine legs and unreached by the tooling job, leaving it present, green, and never actually run. That is the condition a gate test can least afford, and it is the same shape as the defect this suite covers. The sibling suite's own manifest entry is NOT added here: it is already upstream, landed separately after CI went red on exactly this check. Recorded because its absence from this diff is otherwise indistinguishable from not having noticed -- the same reason the dropped row above carries a reason. Deliberately not duplicated: the commit-message and unterminated-quote boundaries are pinned in the quote-straddle suite, and the escape-inside-a-real- span control and the program-path spellings in the suite beside the fix. Not an arm, and recorded because it was reported as one: git worktree add allows with AND without the escape, measured independently by two sessions on separate trees. It is not evidence about escape handling, and whether it should be governed at all is a separate question.
…ix re-opened #1229 on PowerShell (BACKLOG #1229 residual)
MY PREVIOUS COMMIT INTRODUCED A REGRESSION AND THIS IS IT. Honouring a backslash escape inside a
double-quoted span is correct POSIX. But line 999 scans BOTH tool names through ONE matcher --
`$tool -in @("Bash", "PowerShell")` -- and POWERSHELL HAS NO BACKSLASH ESCAPE; its escape is the
backtick. On a PowerShell payload the scan held a span open that PowerShell had already CLOSED,
straddled the live command between it and a later quote, and blanked it.
THAT IS #1229's OWN STRADDLE, RE-CREATED BY THE FIX FOR #1229, ON THE OTHER HOST.
Write-Output "C:\Temp\" ; git -C <governed> reset --hard ; Write-Output "x" ALLOW
... one fewer backslash (control) DENY
... two backslashes (even count) DENY
An ODD count before the closer was the trigger, and the verdict had MOVED against origin/main.
FOUND BY THE SEAT WHOSE DROPPED DESIGN ROW ASSERTED EXACTLY THIS PROPERTY -- a row I argued should
go, and which they dropped agreeing with me. Their commit message called it "a deny no shell
requires". True of sh, FALSE OF POWERSHELL. I made the argument; neither of us checked the second
half; the conservative design was conservative for a reason we did not articulate.
THE FIX: `$PosixEscapes`, threaded from the tool name through Get-ScannableSegments to the scanner,
gating BOTH escape arms. DEFAULT $false, and the direction is the point -- honouring the escape
makes spans LONGER, so it blanks MORE and can hide a command (fail OPEN); refusing it leaves more
text visible to the rules (fail CLOSED). An unrecognised host gets the conservative reading and
only a host known to use backslash escapes opts in.
EVERY TEST ROW IS PINNED TO WHETHER THE COMMAND ACTUALLY EXECUTES ON THAT HOST, measured with an
inert payload that COMPUTES (111*3 -> 333) rather than echoes, so an echo-back could not be
mistaken for a run:
odd-BS PowerShell 333 printed, middle RAN -> DENY
odd-BS Bash "unexpected EOF", nothing parses -> ALLOW is harmless
straddle PowerShell no 333, middle did NOT run -> ALLOW is CORRECT
straddle Bash 333 printed, middle RAN -> DENY
The two ALLOW rows are not concessions: denying them would be a FALSE DENY on a command that
cannot run. "Should this deny" is a question about the shell, not about the gate -- so the rows
record the observation rather than a previous verdict, and a gate that denied everything would
fail them.
Verification: 586 passed / 13 skipped across every test_worktree_gate* module, ruff clean. x12 and
xml extras absent from this venv, so this does not reproduce CI collection.
NOT CLAIMED: the same lens reports this one character also disarming rules 3c and 3d. I have not
reproduced those and they are not addressed here.
…suite The lane based 4d46a2a on 0642792 and reported it as one ahead of 49f425b. It is one ahead of 0642792: the gate-fix seat's four-rule coverage suite (49f425b) landed on this branch in between, so the two lines had diverged one against one. Taking the handed-off tip would have discarded that suite -- the only coverage this PR has that exercises more than a single rule, and the coverage whose absence let the regression this very commit fixes go unnoticed. So this is a merge. Verified in the result rather than trusted for merging cleanly: the merged tree carries 4d46a2a's worktree_gate.ps1 byte-identically (the $PosixEscapes fix, defaulting to false so an unrecognised host gets the fail-CLOSED reading), the coverage suite file, and the manifest at 123 lines classifying both new test files. 458 REMAINS HELD AND AUTO-MERGE STAYS OFF. This commit clears the cause the hold was placed for; it does not clear the hold. The gate-fix seat's refutation phase has not finished, and two of its findings -- the same character reportedly disarming rule 3c (shared core.hooksPath) and rule 3d (another session's worktree) -- are unreproduced and unaddressed here, as this commit's own message states.
…ing about where a span ends (BACKLOG #1229 residual)
SECOND REGRESSION FROM MY OWN ESCAPE FIX, and the host flag could not close this one because the
failing host is BASH -- where the escape is real and honouring it is correct.
The interpreter-argument regex was `[^"]*`, which is escape-BLIND: it stops at the first quote,
including an escaped one. Once the span blanking became escape-AWARE, the two disagreed about
where the argument ends:
bash -c "bash -c \"git -C <governed> reset --hard\""
extraction got: `bash -c \` truncated at the escaped quote, no verb in it
blanking removed: the whole span
result: nothing reached any rule -> ALLOW
MEASURED: origin/main DENY x3, the escape-aware fix ALLOW x3, control DENY on both. The inner
command really runs -- `bash -c "bash -c \"expr 111 \* 3\""` prints 333, so this is execution and
not a parse curiosity.
ON MAIN THE TWO AGREED BY ACCIDENT, both being escape-blind, which left the verb visible OUTSIDE
the span. My fix removed the accident on one side only. That is the whole defect: not a wrong
rule, a rule applied to one of two places that had to move together.
Fix: the double-quoted extraction becomes escape-aware under the same $PosixEscapes flag, so both
sides share one answer. The SINGLE-quoted arm stays escape-blind, matching Remove-QuotedSpans --
sh gives the backslash no special meaning inside a single-quoted word.
Found by the gate-fix seat, who separated introduced-from-inherited by running every shape against
BOTH gates rather than arguing about it, and who stood down mid-dispatch on the collision guard
rather than overwriting this file while I was editing it.
NOT CONFIRMED AND NOT CLAIMED: the same lens reports rules 3c and 3d disarmed by this character.
My reproduction of those does not reproduce -- my CONTROL allows on origin/main too, which means
my harness is not exercising rule 3c at all, so it is evidence about my construction and not about
the gate. Their measurement stands unrefuted; mine is simply not a test of it. Left open rather
than claimed either way.
Verification: 587 passed / 13 skipped across every test_worktree_gate* module, ruff clean. x12 and
xml extras absent from this venv, so this does not reproduce CI collection.
…ail both ways
TEST-ONLY, and it corrects a weakness in my own earlier commit rather than
anything in the gate.
An adversarial pass asked whether this suite could pass VACUOUSLY. Mutation-
tested standalone, with a per-mutant applied-check, it could not:
gate mutated to deny every verb against a governed tree 7 passed, 0 failed
It could see a gate that had stopped denying and NOT one that had started
denying everything. Every row it carried agreed that a deny was correct, so a
fix that widened the gate until the offenders passed would have satisfied all of
them.
THE CAUSE WAS A RULE I APPLIED IN THE WRONG PLACE. The earlier draft dropped its
must-ALLOW rows on the grounds that equivalents live in the sibling suites, and
that a load-bearing fact should be stated once and linked. That is right for
PROSE and wrong for a TEST: a suite discriminates only through the assertions it
actually runs, and delegation is not coverage when the file is read, run, or
trusted on its own.
So each rule now has an ordinary use of the same subject that must be ALLOWED --
a commit message for the primary-tree rule, creating a NEW branch for the hijack
rule, a harmless key for the shared-config rule, a read-only listing for the
worktree-removal rule. Deliberately close to each offender: same subject,
differing only in being legitimate.
After the change, measured the same way:
deny-every-verb mutant 3 failed (was 0)
scanner replaced by the identity function 1 failed (was not scored)
THE MUTATION HARNESS CARRIES AN APPLIED-CHECK AND IT EARNED IT IMMEDIATELY: it
hashes the file and refuses to score a mutant that did not change it. The
identity mutant did not match its anchor on the first run and was reported as
NOT SCORED rather than as survived -- and separately, an ad-hoc probe written
without that check silently measured an unmutated gate and read as a clean pass.
A mutant that fails to apply is indistinguishable from a test that cannot fail.
ONE MUTANT STILL SURVIVES AND IT IS NOT A GAP. Disabling the escape branch
OUTSIDE a span leaves the in-span rule, which makes the span unterminated, and
an unterminated span is emitted RAW -- so the command stays visible and the gate
still denies. Verified rather than reasoned: the mutated gate was driven against
the offender payload directly and returned DENY. There is nothing for the suite
to see, so passing is correct. A plausible cause that retires a finding without
being tested is how a real gap gets closed on paper.
The decisive check is against a REAL gate rather than a synthetic mutant: this
file still goes five rows red against origin/main's hook and green against the
fix on this branch.
…sed (BACKLOG #1229 residual) TWO OF MY COMMIT MESSAGES CARRY A FALSE CLAIM AND A COMMIT MESSAGE CANNOT BE AMENDED ONCE PUSHED, so the correction goes where the next reader will actually be: beside the tests. 4d46a2a and c308cc3 both say rules 3c and 3d are unaddressed and that I did not reproduce them. THEY ARE CLOSED. Verified COLD by the seat that found them, on a rig predating my fix that I did not write: all four PowerShell shapes now DENY where the previous fix ALLOWed, 3c (shared core.hooksPath) and 3d (another session's worktree) included. MY NON-REPRODUCTION WAS MY HARNESS. My control allowed on origin/main too, which means my shapes never exercised rule 3c at all. I said so at the time and left it open rather than claiming either way, which was right -- but the note as written sends the next reader hunting two live defects that do not exist, and an honest hedge that misdirects is still a defect in the record. Gating the escape AT THE SCANNER closed all three rules at once. That is what rule-agnostic cuts both ways means: one character disarmed three rules and one flag re-armed them. AND MEASURING AGAINST MAIN FOUND SOMETHING I DID NOT SET OUT TO FIX: one-level escaped interpreter arg main ALLOW -> DENY INHERITED, now closed two-level escaped interpreter arg main DENY -> DENY my regression, repaired The left column is the point. A fix judged only against the branch it repairs cannot tell "restored" from "improved" -- I would have reported the weaker claim, and the inherited hole would have stayed open with nobody counting it as anyone's. LEFT OPEN AND NOT CLAIMED: twelve further shapes the same lens reports as inherited from main. None introduced here, none fixed here, with the Dispatcher as a disclosure question.
… would neuter this test (BACKLOG #1229 residual)
460's escape-sequence gate flags three invalid escapes in this file. They are real: each becomes a
SyntaxError on a future Python and would fail at COLLECTION, so the module vanishes and a security
regression suite stops running while everything stays green. Exactly the class this file is about.
BUT THE GATE'S PRESCRIBED REMEDY -- "make the whole literal raw" -- IS WRONG HERE AND WOULD HAVE
SILENTLY NEUTERED THE REGRESSION ROW. `_ODD_BS` contains BOTH an invalid `\T` and a valid `\`.
Raw preserves the `\` as TWO backslashes, turning the line's ODD trailing count into an EVEN one
-- and per the measured table ODD is the ALLOW case under test while EVEN DENIES. The remedy
yields a GREEN test of the non-regressing case.
A remedy that turns a failing check into a passing one by changing what is checked is worse than
the warning it silences, and nothing would have reported it.
Caught by the Lander, who measured it by TRANSFORMING THE SOURCE LINE rather than retyping it --
and whose first attempt was itself corrupted by shell escaping, so they redid it programmatically.
I verified the same way: comparing ast.literal_eval before and after, never reading the line.
whole literal raw value preserved = FALSE
escape only the invalid value preserved = TRUE
Applied to all three: `\T`, and two in a docstring (`` \` `` and `\*`) where the same trap
applies, since that docstring also carries valid `\\"` sequences. Rendered text proven identical
for each, so the documentation displays exactly as before.
Verified after: compiles clean under -W error::SyntaxWarning, 16 passed, and the rendered _ODD_BS
still carries an ODD trailing backslash count -- i.e. the row still exercises the ALLOW case it was
written for.
FOR THE 460 AUTHOR, and it is a real defect in the remedy text rather than a nit: "make the whole
literal raw" is unsafe for any literal that ALSO contains a valid backslash escape. Narrow it to
"escape the invalid sequence".
…G #1229 residual) The verified-state note claims this change IMPROVES on main -- a one-level escaped interpreter argument that main allows and this branch denies. It did not say what the construct is, which makes the claim unverifiable: "main ALLOW -> DENY" asks a reader to take the author's word for what was measured, and the entire value of that left column is that it can be re-run. Named now under the owner's ruling that a construct is published only ALONGSIDE THE FIX THAT MAKES IT INERT. This one is inert in this commit and travels in the same change, so it cannot be lifted from here and used against this tree. That is the condition the ruling sets, and it is met here in a way it would not be for the eleven still-live shapes, which stay vaulted and are not named. Confirmed independently before naming it: the seat that found the class re-ran it cold against c308cc3 and 7979ac6 on a rig I did not write, with four controls, and reports main ALLOW -> DENY. The improvement claim therefore does not rest on its author's measurement. Also records, for the vault inventory, that the inherited list is now ELEVEN rather than twelve -- this shape came off it.
… (BACKLOG #1229 residual)
Remove-QuotedSpans kept the git token of any closed span whose leaf was `git` or
`git.exe`, lowercased, so a quoted PROGRAM path stayed visible to the
case-sensitive rules. That was right about programs and wrong about everything
else: it read `cp -r "/c/backups/Git" restore` -- a backup directory followed by
an ordinary word that happens to be a git verb -- as a git command. At least 15
such shapes were measured DENY here and ALLOW on the blob before the emit landed.
THE FALSE DENY WAS NOT THE WORST OF IT. An argument-position emit makes its line
look like a git command carrying a gated verb, and rule 3 hands the FIRST such
line it finds to Test-WorktreeHijack. Measured from inside a linked worktree,
two lines -- copy a directory named Git, then a real `git switch` onto a free
branch -- the hijack came back ALLOW. So this closes a fail-OPEN as well as a
fail-CLOSED defect, with one predicate.
Test-GitProgramPosition asks whether a command BOUNDARY is reachable leftward
across tokens transparent to command dispatch: pairs this scan already blanked,
assignment prefixes, an allowlist of wrapper words, their options and operands,
completed redirections and shell keywords. A bare word that is none of those ends
the chain, which is exactly what makes cp, mv, ls, echo, python and docker stop
denying while sudo, env, FOO=1, timeout 5 and find -exec keep denying.
BOTH ENDS, per the owner ruling of 2026-08-20 and recorded at the site:
GAINED -- the argument-position family allows in every case spelling, including
`cp -r "/c/backups/git" restore`, which DENIED on origin/main as well. That
is a deliberate DENY-to-ALLOW move against main, not only a repair.
PAID -- an UNLISTED wrapper word fails open: `myrunner "/usr/bin/git" -C <gov>
reset --hard` allows where main denied. Pinned as a tripwire that reds when
somebody closes it.
The `.exe` spelling is governed by the same predicate rather than short-circuited.
A short-circuit costs nothing against the measured false-deny corpus, but it was
measured to leave the shadow fail-open ALLOWing through a file named `Git.exe`,
so the uniform rule is the one that actually closes the hole.
MEASURED over the real hook, 93 rows against origin/main and HEAD: zero controls
moved against HEAD, all 38 program-position shapes deny, the argument family and
the rule 3c/3d argument-position false denies allow, and all three shadow shapes
deny. Pytest with this change: 535 passed 1 skipped over the ten other
test_worktree_gate modules plus test_gate_rule_scan_agreement and
test_gate_installed_parity, 57 in test_worktree_gate_escaped_quote, 28 in
test_worktree_gate_hijack. The one skip is the installed-parity test, which skips
while the gate source is uncommitted and will assert once this lands -- an owner
reinstall from a current checkout is the remedy; install-gate.ps1 refuses under
CLAUDECODE and CI has no installed gate to compare, so green CI is not evidence.
…name (BACKLOG #1229 residual)
The interpreter recursion asked one question -- did the flag SHAPE match -- and
then applied the OUTER tool name's escape convention to whatever it extracted.
Both halves were wrong, in opposite directions, and each alone was measured
insufficient.
ONE: a flag match is not evidence that the program executes its argument.
`$shFlag` is `-[a-z]*c` under (?i), so it matches -C, -ic, -rc, -static, -sync
and -exec, and `$cmdExeFlag` walks an ordinary POSIX path one component at a
time. Over a hand-built axis, 28 of 33 NON-interpreter invocations matched and 18
of 36 real interpreter invocations did not. So `grep -c 'git reset --hard'
/c/logs/history.log` -- an ordinary search of a shell history -- DENIED, and so
did rg, ag, curl, sort, uniq, wc, cut, head, tail, ls, tar, gzip, md5sum, cmp,
diff, rsync, gcc -static and make -C. Driven on the real binaries with a payload
that COMPUTES (expr 111 \* 3 -> 333), every one of those left no marker while
bash -c and sh -c both printed 333. Get-FlagOwner now answers the question, and a
program that does not execute its argument gets no recursion.
TWO: the escape convention belongs to the interpreter that RECEIVES the payload.
A Bash tool call invoking pwsh applied POSIX backslash rules to a PowerShell
payload, so the scan held open a span PowerShell had already closed and blanked
the gated command between it and a later quote. Measured on this box:
pwsh -NoProfile -Command '$d = "C:\Temp\" ; 111*3 ; Write-Output "x"' -> 333: it RAN
bash -c '$d = "C:\Temp\" ; expr 111 \* 3 ; echo "x"' -> unexpected EOF: INERT
Same characters, opposite correct answers. One flag per line cannot express that,
which is why each extracted payload now carries its own convention taken from
Get-FlagOwner, while the outer line keeps the outer host's rules -- the outer
line really is the outer host's. Three shapes go from a fail-open ALLOW back to
DENY: pwsh -Command, pwsh -c and powershell -Command carrying that straddle.
THE LEFTWARD SCAN IS WHAT MAKES IDENTITY WORKABLE, and it overturns a claim this
file made. The $cmdExeFlag note said the ls /usr/src/c false deny was "a
program-identity problem, and that is why it is not fixed here", resting on five
candidate mutants each defeated by something that executes. Four of the five --
echo hi;cmd /k, (cmd /mnt/c, cmd /d /Q/C, cmd /usr/src/c -- break ADJACENCY, not
identity: every candidate asked whether the token IMMEDIATELY LEFT of the flag
was a cmd spelling. A scan that walks left past options and switch components,
bounded by the last command separator, survives all four, measured. The fifth, a
renamed or aliased cmd.exe, is not closed and is not claimed.
So tests/test_worktree_gate_interpreter_sigils.py's pinned false-deny tripwire
fired, exactly as its own docstring said it would. It is rewritten to assert the
closure with the four adjacency counterexamples as its other arm, and the note it
pointed at is corrected in place rather than deleted.
DISCLOSED COST: `myrunner -c '<gated>'` -- an unknown program with a -c flag --
goes DENY to ALLOW. If such a program is a real interpreter that is a fail-open.
It is the same class as the 18 interpreter spellings the flag matcher already
misses (perl -e, node -e, eval, ssh host CMD), and the previous catch was an
accident of the flag shape rather than a decision, but it is a deliberate move
against origin/main and is pinned as a tripwire.
NOT CLOSED and not claimed: PowerShell's own escape is the BACKTICK and that
convention is modelled nowhere. A -Command payload that closes its inner strings
with a backtick-escaped quote straddles the same way, and allows on origin/main,
on HEAD and here -- measured to really run. Remove-QuotedSpans has a two-valued
convention where three are needed. Named by subject; no number cited.
MEASURED over the real hook, 86 rows against origin/main and HEAD: all three
fail-opens deny, all 26 non-interpreter false denies allow, every one of 30
interpreter controls still denies including the four adjacency shapes, and the
six must-allow controls are unmoved. Zero rows changed by this layer across the
ten pre-existing rig specs.
…G #1229 residual) test_an_UNLISTED_wrapper_word_is_a_known_open_residual carried two rows under one heading, and the heading was true of only one of them. `myrunner` really is absent from Test-GitProgramPosition's vocabulary. `setarch` is PRESENT -- what ends the chain there is its OPERAND, `x86_64`, a bare word and therefore not transparent. That matters because the two have different remedies, and a reader acting on the wrong one would add `setarch` to a list that already contains it and watch nothing change. So the allowlist has two ways to lose, and both are now named. The obvious widening -- let a wrapper be followed by one bare operand -- was measured rather than reasoned about before being declined: it would close the setarch row and cost `watch ls "/c/backups/Git" clean`, which is main=ALLOW, this build=ALLOW today and would become DENY. A real shape traded for a narrow one, so the row is disclosed instead of closed. Found by re-reading the committed docstring against the vocabulary it describes, which is the check that would have caught it before it landed.
… residual) Get-FlagOwner's docstring said the five shapes that defeated five earlier program-token candidates were "all five measured to survive" the bounded leftward scan. That is true of four of them and false of the fifth, and the fifth is the one a reader needs to know about, because it is the cost this change takes. The five are `echo hi;cmd /k`, `(cmd /mnt/c`, `cmd /d /Q/C`, an alias, and a renamed copy of cmd.exe. The first three break ADJACENCY -- every earlier candidate asked whether the token IMMEDIATELY LEFT of the switch run was a cmd spelling -- and all three deny under a scan that walks left past options and switch components. The last two break IDENTITY, and an allowlist cannot close them: an unknown name gets no recursion. The $cmdExeFlag note beside it already said this correctly. The function's own docstring did not, and a completeness claim that is one member too wide is the liability CLAUDE.md section 11 names.
…LOG #1229 residual)
Remove-QuotedSpans keeps the git token of a closed quoted span so a quoted PROGRAM path
stays visible to the rules. Two changes were layered on top of that and both are withdrawn
here:
(a) a case-INSENSITIVE emit canonicalised to lowercase, so `GIT.EXE` -- a real Windows
spelling the case-SENSITIVE rules downstream skip -- would still present a verb;
(b) Test-GitProgramPosition, added because (a) then read `cp -r "/c/backups/Git" restore`
as a git command, keeping the token only where the span is dispatched as a program.
WHY, and it is not the false denies. (b) bought those back at the price of two fail-OPENS on
shapes origin/main DENIES, measured over the real hook:
cmd /c "<...>\Git\bin\git.exe" -C <governed> reset --hard main=DENY experiment=ALLOW
. "<...>\Git\bin\git.exe" -C <governed> reset --hard main=DENY experiment=ALLOW
(the second a PowerShell dot-source, on a PowerShell tool call)
Spending a security gate's DENY to buy a tidier false-deny profile is the wrong direction.
Re-measured after the revert, both rows are DENY again. Get-FlagOwner -- the interpreter
axis, 548cf50 -- is KEPT and untouched; its wrapper set is a different mechanism from (b)'s
and keeps its own coverage in tests/test_worktree_gate_interpreter_flags.py.
The emit is now case-SENSITIVE (-cmatch, no ToLowerInvariant). Not a regression: origin/main
ALLOWs `GIT.EXE` in program position too, by another route -- its collapse regex is
case-insensitive but substitutes $1, preserving a case the rules then fail to recognise.
TESTS RETIRED, each because the behaviour it asserted exists nowhere any more:
- test_a_wrapper_prefix_does_not_move_a_quoted_git_program_out_of_program_position
(24 rows) pinned Test-GitProgramPosition's wrapper vocabulary. The function is gone, and
every row used the GIT.EXE spelling, which measures ALLOW on origin/main as well.
- test_an_UNLISTED_wrapper_word_is_a_known_open_residual asserted ALLOW for `myrunner` and
`setarch x86_64` ahead of a quoted lowercase git path. Both measure DENY on origin/main
and on this tree, so the residual it disclosed does not exist. Its own note says to
delete the row rather than restore the ALLOW.
- test_the_argument_position_ALLOW_is_a_RECORDED_WEAKENING_for_the_lowercase_spelling
asserted ALLOW for `cp -r "/c/backups/git" restore` from primary.parent. It PASSED and
could not have failed: from that cwd no governed target resolves, so the verdict comes
from the target resolver and not from the emit. Run from INSIDE the governed repo the
same line measures DENY on origin/main and on this tree -- the opposite of its claim.
- test_the_program_path_token_survives_every_spelling lost its `GIT.EXE` and `Git` rows and
keeps `git` and `git.exe`. The two dropped spellings measure ALLOW on origin/main.
WHAT REPLACED THEM, so neither end of the trade lives only in prose:
- the argument-position rows now run from INSIDE the governed repo, where the emit is the
only variable, and each carries a control varying THE LEAF CASE ALONE. Measured 14 of 14,
both blobs: Title-cased leaf ALLOW, lowercase leaf DENY.
- the uppercase quoted PROGRAM spelling is pinned as a known-open residual (asserts ALLOW).
- the lowercase argument-position leaf is pinned as a known-open FALSE DENY (asserts DENY).
Verification: 33-row and 28-row probe matrices driven over the real hook against origin/main
and this tree returned IDENTICAL verdicts on every row -- no weakening and no tightening.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…redicate (BACKLOG #1229 residual) Prose only. Every assertion in tests/test_worktree_gate_hijack.py is unchanged and every row passed before and after: `cp -r "/c/backups/Git" switch` on line one must not eat a real `git switch <free branch>` on line two. What the prose said was that these rows guard "the position predicate" and Test-GitProgramPosition. That function was reverted, so the comment pointed at nothing and the next reader would have scored the rows as dead coverage. What they actually guard is the CASE-SENSITIVITY of the emit in Remove-QuotedSpans, which is why the leaf axis is `Git` / `Git.exe`. A case-insensitive emit hands line one a git token, rule 3 records that segment, and rule 3b returns ALLOW on the hijack behind it. So relaxing the case rule is a fail-OPEN on 3b and not only a false-deny question -- which is precisely why this coverage lives here rather than beside the false-deny rows in tests/test_worktree_gate_escaped_quote.py. Also corrected: "the unconditional emit" now reads "a case-INSENSITIVE emit". The emit IS unconditional in position after the revert, so the old wording named current behaviour while meaning the withdrawn experiment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t a comment (BACKLOG #1229 residual)
The program-position experiment was reverted because it moved two shapes from DENY
to ALLOW while origin/main denies both:
cmd /c "<...>\Git\bin\git.exe" -C <governed> reset --hard main=DENY experiment=ALLOW
. "<...>\Git\bin\git.exe" -C <governed> reset --hard main=DENY experiment=ALLOW
(the second is a PowerShell dot-source, on a PowerShell tool call)
That reason lived only in prose -- the narrative block at the top of the file and two
assertion-failure strings -- so nothing executable would have reported it if the
predicate came back. Measured: restoring the experiment blob reds 16 tests, and every
one of the 16 is a FALSE-DENY row whose own text instructs the reader to delete it.
Followed literally, the suite's stated remedy lands both fail-opens green.
The nearest existing coverage is not this shape: test_worktree_gate_interpreter_flags
asserts DENY for `cmd /c '<whole gated payload>'`, where git sits INSIDE the quoted
span. Here git.exe IS the quoted span and the verb is outside it, which is what the
position predicate changed.
Measured on the real hook, cwd = the governed repo:
both rows main=DENY fb93c9c=ALLOW HEAD=DENY
control, UNGOVERNED target ALLOW on every blob
The ungoverned control is what stops the row degenerating into "this gate denies any
cmd /c"; the fb93c9c column is the anti-vacuity check -- the test genuinely reds if
the predicate returns, rather than passing against anything.
Docstring says DO NOT delete this row on red, inverting the two neighbouring tripwires,
because a red here is a re-opened hole rather than a closed one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nwritten #1305 -- the worktree gate matches git by SPELLING, so a case variant bypasses every rule. Measured against origin/main driving the real hook as a subprocess: a capitalised spelling of the program name ALLOWS where the lowercase spelling DENIES, with a benign negative control allowing on both. Not confined to one rule -- checkout allows too. No quoting, wrapper or escape involved. Windows resolves the spellings to the same executable so all of them RUN; the gate's comparison is case-sensitive so only one MATCHES. It is matching a spelling and calling it a program. Reproduces identically on origin/main and on the unlanded lane branch, so #1229's residual work neither opened nor closed it. Filed in the ledger rather than vaulted: this guard is coordination tooling, is not shipped in the wheel, and is not graded by any ASVS cell. The exhaustive spelling-by-rule matrix is kept in the episode note rather than here. #1301 -- a banner citing a sha must cite a commit naming its own item. The #1022/#1259 transposition passed every gate: valid Markdown, unchanged item count, untouched status glyph, and the misplaced paragraph carried no glyph of its own so parse_items had no second banner to object to. The signal present throughout: the overwritten banner cited two shas whose subjects both ended in that item's number; the paragraph replacing it cited a sha naming a different item. Filed as a PARTIAL control and described as one -- it fires only on banners citing shas. The forbidden-content gate blocked the first attempt at this commit over a branch slug in #1305's prose. Removed rather than allowlisted, per the gate's own instruction. Both appended at the file tail, the lowest-conflict edit against the re-scored main. parse_items: 314/230/84 before, 316/232/84 after -- the expected +2 open, no other movement.
…aude/builder-1-160cec
Committed from the worktree that OWNS the two allocations, which is what the
ledger gate is for rather than something to route around. BACKLOG #1301 and
#1305 are allocated to this worktree (claude/builder-1-160cec); a merge made
from any other worktree trips ledger_check.owns(), and alloc.ps1 has no
transfer verb. Nothing was renamed, copied or --no-verify'd.
Also brings in origin's three commits, which this branch's local tip lacked.
That side and this one had DIVERGED, so a force-push would have discarded the
four-rule coverage suite -- the third time this lane has met that trap.
Conflicts, each resolved on what the hunk actually is:
* #1022 -- TOOK MAIN. Both sides retire the same 2026-08-15 do-not-build
banner; main's pass is later (08-21 vs 08-20) and names the two commits
PR #394 carried, so this lane's copy is superseded, not lost.
* #1268 -- TOOK THIS LANE. Main still scores it open because on main both
limbs do survive; this branch is what ships them. Taking main's re-score
would have landed the fix under a banner saying it was not built, and
dropping that banner is what the one-item-one-status rule requires.
* the new-item block -- TOOK BOTH. A pure insertion collision: main appended
#1299-#1317, this lane appended #1305 and #1301, no number claimed twice.
Either side alone silently deletes real filings.
* docs/adr/README.md -- TOOK BOTH, ordered 0169, 0170, 0171.
backlog_status_check: 568 items, each declaring exactly one status.
worktree_gate.ps1 verified content-identical to this lane's fixed copy after
both merges, so neither merge quietly reverted the fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolves the one conflicted file, docs/BACKLOG.md, in two hunks. Files nothing
and fixes nothing -- no item's disposition moves here.
HUNK 1, under #1317: took main whole. The scoping clause this branch carried
("That cell's own ruling is a separate question about allowlist width; this item
is the build") is ALREADY the last line of main's side, its sentence completing
past the conflict at "which both readings of the cell now require". The lander's
approved plan said re-attach it; re-attaching would have DUPLICATED it. Asserted
in the resolution script rather than trusted, so the merge fails rather than
silently duplicating if that ever stops holding.
HUNK 2: kept both sides. A pure tail append -- main adds #1318-#1323, #1327,
#1328; this branch adds #1305 and #1301. Neither number is on the other side.
VERIFIED with parse_items, not a hand-rolled scan: 340 items resolved against 338
on main and 332 on HEAD, ZERO lost from either side, added-beyond-main is exactly
[1301, 1305], no duplicate numbers. Negative control main-minus-HEAD returns
[1318..1323], so the set comparison can distinguish and the zeros are not vacuous.
…BACKLOG #1314) Both SMTP connectors branch three ways on TLS posture. The `not use_tls` and `not tls_verify` arms each carry an escape gate AND an absolute credential gate. The verified arm carried a body guard and no credential gate at all -- while `tls_check_hostname` is read right below it and fed to the context builder. So `use_tls=true` + `tls_verify=true` + `tls_check_hostname=false` + a username constructed silently, and `smtp.login()` then ran against any peer whose cert chained to the anchor, whatever name it was issued to. Adds a third credential gate to each, matching the wording and the ABSOLUTE placement of the two beside it. Keyed on no escape, deliberately: in both existing arms the escape governs the BODY posture and never the credential, and a hop must not be able to attest its way to a credentialed unverified-name session. DIRECT HAD NO `else` ARM AT ALL -- the chain fell straight through to the context build -- so that branch is new rather than extended. The gate is inlined in both files rather than extracted, which is this cell's stated convention: the verify-off arms say a shared helper was refused because "a third spelling is how the next bug gets written". VERIFIED, and the controls are the point: - RED FIRST on email: "DID NOT RAISE ValueError" before the gate existed. - Direct's tests were written AFTER its gate, so they had never been red. Disarmed the gate to `if False and ...`: the refusal test failed and a control still passed. Restoring it revealed a second thing worth recording -- `git checkout --` reverts to HEAD, which never held the fix, so it destroyed the gate instead of the mutation. Caught by asserting the restore rather than assuming it. - NEGATIVE CONTROL both files: `tls_check_hostname=false` with NO username still constructs, so the gate is keyed on the credential and does not widen the arms. - POSITIVE CONTROL both files: a fully verified hop WITH credentials still constructs, so the refusal tests are not green by refusing everything. - 66 passed across both modules; ruff format, ruff check and mypy strict clean. Not measured: no live handshake was run here. The item already carries one. Severity is conditional -- zero deployments, so this is what a deploying site would hit, not an exposure today.
…5 scope 2) Band B1 item 2 of four. Adds `harden_crl_check(ctx, crl_file)` beside `harden_verify_flags`, whose docstring says in as many words that it does strict RFC 5280 path validation and NOT revocation, plus `read_crl_facts` in pki.py as the sibling of `read_cert_facts` that the item names. RE-MEASURED THE TRAP TABLE FIRST, because the item says to and forbids inheriting it. CPython 3.14.6 / OpenSSL 3.5.7, TLS 1.2 pinned, throwaway synthetic PKI: arm crls good client revoked client CA only, no flag (SHIPPED) 0 ACCEPTED ACCEPTED <- the gap cafile= CA + FRESH crl, flag 1 ACCEPTED REFUSED revoked cadata= CA + FRESH crl, flag 0 REFUSED REFUSED <- trap 1 cafile= CA + STALE crl, flag 1 REFUSED REFUSED <- trap 2 All four inherited rows reproduce. THE FIFTH ARM IS NOT MEASURED AND I AM NOT RECORDING IT AS RULED OUT: `capath=` needs a hashed directory and `openssl rehash` was unavailable here, so what that arm tested was an UNHASHED directory failing, which is expected and answers nothing. The item's capath question stays open. Both traps are ASSERTIONS in the helper, not comments: - loads through `cafile=` only, then requires `cert_store_stats()["crl"] >= 1`. `cadata=` sets the flag with zero CRLs loaded and then refuses EVERY client with "unable to get certificate CRL", silently. The count is the only thing that separates "loaded" from "silently ignored". - refuses an already-expired CRL at construction, because past nextUpdate OpenSSL refuses every client rather than just revoked ones -- a PKI housekeeping lapse becomes a total outage whose first symptom is every partner dropping at once. - refuses a missing file rather than degrading to no revocation checking. tls_policy.py stays STDLIB-PURE at import time: it declares itself "pure stdlib ssl helpers" and touches cryptography only in two prose comments, so the parsing lives in pki.py and both imports here are function-level. VERIFIED: red-first (ImportError, symbol absent), then 91 passed in the module; ruff format, ruff check and mypy strict clean. NOT YET DONE, so this is not the item: scope 1 (the tls_crl_file setting on three factories + ApiSettings), scope 3 (the posture-keyed fail-closed refusal), scope 4 (the pre-expiry alarm on CertExpiryRunner), and the real-handshake rig. This commit asserts the FLAG IS SET; it does not yet assert a revoked client is refused, and those are different claims.
…ed (BACKLOG #1005) The previous commit set a flag and said so. "The flag is set" and "a revoked client is refused" are different claims, and only the second is worth having. The item makes the same point: on the two builders it touches most there is no real client-cert handshake anywhere, and test_mllp_tls.py asserts `ctx.verify_mode == ssl.CERT_REQUIRED` and stops. Three tests, driving an actual mTLS handshake on 127.0.0.1 against a context built by harden_crl_check. TLS 1.2 is pinned so client auth happens IN the handshake -- under 1.3 the client certificate arrives after the server has finished and the refusal surfaces on a later read instead, which is how a revocation test can look green while proving nothing. 1. A revoked client IS refused, and the assertion reads OpenSSL's reason rather than merely that the handshake failed. 2. A good client IS ACCEPTED by the same context. This is the control that separates working revocation from a context refusing everyone -- which is exactly what trap 1 produces, and what a flag assertion cannot tell apart. 3. WITHOUT the CRL, the revoked client GETS IN. The shipped gap the item is filed against, pinned deliberately as a negative control: if this ever starts failing, revocation arrived by some other route and the item's premise needs re-deriving rather than the test relaxing. Synthetic throwaway PKI in a module-scoped tmp fixture. No PHI, no network beyond loopback, nothing persisted. 94 passed in the module; ruff format, ruff check clean.
…G #1005 scope 1) Band B1 item 1, wired in the SAME commit as the setting on purpose. A setting the factories accept and nothing reads is a silent no-op, which is the exact shape this whole item is about -- so it never exists in that state. SURFACE: `tls_crl_file` on MLLP(), Http() and DICOM(), plus ApiSettings. tls_client_crl_file beside tls_client_ca_file. Each declaration matches its OWN local shape -- MLLP's union spans two lines, Http's is one, DICOM's takes EnvRef across three -- rather than one imposed spelling. There is no separate TOML key list to edit: connections_file.py:290 states "the factory IS the schema", and _INBOUND_KEYS allow-lists top-level entry keys, not settings keys. THE ITEM'S LINE NUMBERS WERE STALE, so every coordinate was re-derived from the symbol. tls_ca_file resolves in FIVE factories; Email() and Direct() also carry it and are outbound SMTP, outside this item. PLACEMENT IS THE LOAD-BEARING PART AND IT DIFFERS FROM ITS SIBLING'S. harden_crl_check loads into the trust store, so it must follow the CA load, inside the mTLS branch. In mllp.py and dicom.py harden_verify_flags already sits after the CA. IN api/tls.py IT SITS BEFORE IT -- so placing the CRL call beside its sibling there would load a CRL into an empty store, producing a context with the check flag set and zero CRLs, which refuses EVERY client. That is trap 1, reached by copying a neighbour. One wiring covers two listeners: http_listener.py calls _mllp_ssl_context, so the inbound HTTP listener inherits it. THREE ASSERTIONS ON THE MLLP BUILDER, the module the item names as stopping at `ctx.verify_mode == ssl.CERT_REQUIRED`: - configured -> cert_store_stats()["crl"] >= 1 AND the flag set. The count is what separates "loaded" from "silently ignored". - NEGATIVE CONTROL, pinning the shipped gap: mTLS on, no CRL -> no revocation flag. If this ever fails, revocation arrived by another route and the premise needs re-deriving. Do not relax it. - PLACEMENT GUARD: a CRL with no mTLS is not loaded -- nothing to revoke when no client certificate is requested. VERIFIED: mutation test, disarmed the wiring to `if False and ...` -> exactly the configured-case test failed, both controls survived; restore byte-identical by sha256 (not `git checkout --`, which reverts to HEAD and would have deleted the wiring). 296 passed, 3 skipped across the six affected modules; ruff format, ruff check and mypy strict clean. STILL OPEN on the item: scope 3 (posture-keyed fail-closed refusal) and scope 4 (pre-expiry alarm on CertExpiryRunner). And `capath=` remains UNMEASURED, not ruled out -- no rehash tool on this box.
…use it (BACKLOG #1005) I recorded capath= as "cannot be tested here" because my probe reported no rehash tool. THAT WAS FALSE and a peer caught it. Git for Windows ships both openssl and c_rehash. Measured now, with controls: arm crls good revoked capath= hashed dir 0 ACCEPTED REFUSED: certificate revoked cafile= bundle [POSITIVE CONTROL] 1 ACCEPTED REFUSED: certificate revoked cafile= CA only, no flag [BASELINE] 0 ACCEPTED ACCEPTED capath= ENFORCES REVOCATION IDENTICALLY. But cert_store_stats()["crl"] reports ZERO for it, because a hashed directory is read LAZILY at verification rather than at load time. So the >= 1 assertion in harden_crl_check -- exactly right for cafile=, and the whole defence against trap 1 -- IS NOT A VALID LIVENESS CHECK FOR capath= and would REJECT A WORKING CONFIGURATION. Recorded in the docstring where the next person will hit it, rather than left as an open question they would re-derive. WHY MY FIRST ANSWER WAS WRONG, three faults in one probe, each of which alone produces a confident false negative: 1. subprocess could not CreateProcess c_rehash -- it is a SHELL SCRIPT, not an exe -- and I swallowed the FileNotFoundError with a bare except and no diagnostic. 2. `openssl rehash` on this build PRINTS "Not available; use c_rehash script" AND EXITS 0. Had my subprocess resolved it, my returncode==0 check would have recorded a hashed directory that was never hashed. An exit code reading success from a tool that just said it cannot do the job. 3. Passing a Windows path into `bash -lc` hashed nothing and still returned 0. The guard that saved it was asserting the .r0 file EXISTS before reporting any arm, rather than trusting the rehash step. Without it, three separate mechanisms were each ready to hand me "capath does not work" from a directory OpenSSL was never given. 94 passed; ruff format, ruff check, mypy strict clean.
… instance (BACKLOG #1005 scope 3) Band B1 item 3. The revocation sibling of the cleartext exposed-gate, and it fires on the OPPOSITE condition: those refuse a listener with NO TLS, this refuses one whose TLS is correct but whose client certificates are never checked for revocation. Measured on this tree, a revoked-but-chain-valid client is ACCEPTED. THE ITEM DID NOT NAME THE ACTUAL BLOCKER, which was a missing model field. Source carried tls_hop_attested and tls_hop_attested_reason; Destination carried those PLUS tls_revocation_attested. So there was nothing on the inbound side to key an attestation on. Adding it first was the real first step -- every sibling refusal here pairs with a per-connection attestation, and a revocation refusal with no escape would be the only control without one, refusing a site whose PKI covers revocation outside the engine. THE GATE keeps the sibling's three rungs in the sibling's order: attested permits; an UNSTAMPED posture permits, because that call ran outside the enforced gate and must never acquire a new refusal; otherwise refuse only when the instance is BOTH enforcing AND PHI. NO BLUNT PROCESS-WIDE ESCAPE, deliberately. MEFOR_ALLOW_INSECURE_TLS governs weakened TLS, and a listener that verifies its peers correctly but skips revocation is not a weakened-TLS hop. Reusing that env var would let one variable silence a control it was never scoped to. SCOPED to the three connectors that can hold a client certificate -- MLLP (which also serves the inbound HTTP listener), HTTP and DIMSE. NOT raw TCP/X12: they have no TLS option, so there is no certificate to revoke. Note the name mismatch that would bite a copy-paste: the FACTORY is DICOM() and the CONNECTOR TYPE is DIMSE. NINE TESTS, one control and seven ladder rungs around it: - the refusal itself, and the same refusal on DIMSE and HTTP - POSITIVE CONTROL: a configured tls_crl_file passes, so the refusal can be SATISFIED and not merely avoided -- without it the refusal test would pass equally against a gate that refuses every mTLS listener - the per-connection attestation passes - non-PHI warns, non-enforcing warns, unstamped posture never refuses - PLACEMENT GUARDS: TLS without mTLS is not a revocation gap, and a connector with no mTLS surface is ignored VERIFIED by mutation: disarming the refusal to `if True or ...` failed EXACTLY the two refusal tests and left all seven ladder and guard tests passing. Restore byte-identical by sha256, from a saved copy rather than `git checkout --`, which reverts to HEAD. 257 passed, 3 skipped across seven modules; ruff format, ruff check, mypy strict clean. REMAINING on the item: scope 4, the CRL pre-expiry alarm on CertExpiryRunner.
…wn (BACKLOG #1005 scope 4) Band B1 item 4, and the last of the four. The freshness PREFLIGHT already landed with harden_crl_check (a7afcff) -- it refuses an already-expired CRL at construction. This is the other half: warn BEFORE that happens. WHY A CRL BELONGS IN THE CERT MONITOR AT ALL: the operator question is identical -- "is a file I depend on about to expire". CertExpiryRunner already reads notAfter off every served cert and alerts inside a warn window. A CRL's nextUpdate is the same shape, so MonitoredCert gains a `kind` discriminator (defaulted, so every existing construction is unchanged) and _inspect dispatches to read_crl_facts. BUT IT ALERTS DOWN A SEPARATE SINK METHOD, AND THAT SEPARATION IS THE POINT. An expiring CERTIFICATE degrades one identity and is fixed by reissuing it. An EXPIRED CRL makes OpenSSL refuse EVERY client presenting a certificate under that issuer -- not merely revoked ones -- so it is a total interface outage fixed by a PKI refresh. Emitting both down cert_expiry would hand an operator ONE STRING FOR TWO CAUSES WITH OPPOSITE REMEDIES, which is the defect this fleet spent tonight finding in an unrelated banner. The logging sink escalates an expired CRL to ERROR rather than WARNING for the same reason: the listener is already down, not approaching a deadline. INBOUND ONLY, deliberately: a CRL verifies peers we REQUIRE certificates from, and only an inbound listener does that. NOT DONE, AND NAMED RATHER THAN OMITTED: the API listener's own ApiSettings.tls_client_crl_file (added in 5790321) is NOT yet watched. Threading it needs a new Engine constructor parameter from its caller, which is a wider change than this item's core. The three connection listeners are covered; the API one is not. FIVE TESTS: - a CRL inside the window alerts crl_expiry and NOT cert_expiry -- asserting crl_calls alone would pass if the runner fired BOTH, so both are asserted - an expired CRL reports negative days - POSITIVE CONTROL: a fresh CRL alerts NOTHING, so the two above are not green against a monitor that alerts on every CRL handed to it - a cert and a CRL on one connection route to their own methods, which is the realistic configuration and where the separation earns its keep - an inbound tls_crl_file is collected from a real Registry, built with the real InboundConnection and ConnectionSpec types rather than a fake shape VERIFIED by mutation: disarming the crl dispatch failed EXACTLY the three routing tests and left the fresh-CRL control and the collection test passing. Restore byte-identical by sha256 from a saved copy. 297 passed across seven modules; ruff format, ruff check clean; mypy strict clean on all 267 source files.
…aude/builder-1-160cec
… diagnostic limb) #1245's AVAILABILITY defect is already closed -- _unclaimed_bootstrap is now the single predicate both readers call, and the confirming experiment exists. What the item keeps open is the DIAGNOSTIC limb, and it fences it explicitly: "DO NOT CLOSE THIS ON A GREEN RE-RUN. A green re-run is consistent with an intermittent, not evidence against one." THE RECORDED FAILURE IS `must_change_password` FALSE AFTER admin_reset_password -- NOT the retirement assertion below it. One investigation pass was already spent on the wrong assertion; the item says so in as many words, so the comment says it here where the next reader is standing. A bare assert cannot distinguish three causes, and all three produce an identical red: (a) the write never landed (b) it landed and something reset it (c) the read path is wrong A PAIRED READ AT THE SAME INSTANT -- the store record AND the raw row -- splits them. Uses the same `store._db` seam four other tests in this file already use, rather than introducing a second idiom for reading a row. TWO LINES, AND IT DOES NOT REQUIRE REPRODUCING THE FAILURE FIRST. That is what makes it worth adding before anyone tries to, and it is the item's own argument. VERIFIED BY FORCING IT, because a diagnostic whose failure path never runs is decoration. Injected the (a/b) condition -- cleared the column before the paired read -- and the assertion produced: #1245 intermittent: store record must_change_password=False, raw row must_change_password=0. DISAGREE -> the read path is wrong (c). BOTH falsey -> the write never landed or was reset (a/b), and the raw value tells you which by whether the row exists at all. Both values render and a reader lands on (a/b) without re-deriving anything. Injection reverted, file byte-identical by sha256. 139 passed across three auth modules; ruff format and check clean. NOT CLOSING THE ITEM. The item forbids closing on a green re-run, and this commit adds instrumentation rather than finding the mechanism. Six candidates are already refuted by reading in the item body; a seventh is deliberately not offered as a story.
…G #1276 part A) Owner ruling 2026-08-22, option 3, quoted by the liaison: "engine always serves TLS, supersede 0143 in the same PR". Before this, tls_enabled was literally bool(tls_cert_file) with both keys defaulting None, so an unconfigured engine opened a CLEARTEXT socket. An operator certificate still always wins -- this is a fallback BENEATH [api].tls_cert_file, never a replacement. UNCONDITIONAL ON PURPOSE. A conditional scheme is what let the tray, the harness and the DAST target each decide the scheme their own way; that divergence is the defect the item exists to remove, and you cannot disagree about a scheme that has no conditional. THE EXISTING TEST CAUGHT A REAL BREAK AND I FIXED THE CODE, NOT THE TEST. `tls_terminated_upstream` (+ trusted_proxies) declares a reverse proxy terminating TLS in FRONT of the engine and speaking plaintext to it. My first cut minted there too, which does not harden that deployment -- it BREAKS the proxy's own hop. So "always serves TLS" means the engine never leaves a hop unprotected, NOT that it terminates TLS in every topology. The helper returns None there and the serve path skips the wiring, exactly as before. ONE TEST IS DELIBERATELY INVERTED, and it is renamed so the diff cannot be misread: test_serve_loopback_without_tls_passes_no_ssl_factory asserted "plaintext loopback: no TLS wiring", pinning the behaviour the ruling removes. It now asserts the factory IS wired and the pair IS on disk. That case -- loopback -- is precisely ADR 0143's premise, which is why the ruling pairs this with superseding it. DECISIONS I MADE AND THE ALTERNATIVES REJECTED, both delegated to me explicitly: - PATH: beside the store database. That directory is already the engine's own writable state, already operator-controlled via --db / [store].path, and is NOT operator-authored config -- which keeps dead option 1 (engine edits operator TOML) dead. REJECTED: a new [api].tls_generated_dir knob, a setting for a question with one sensible answer. - LIFETIME: 365 days, inheriting the `cert self-signed` CLI default rather than inventing a second lifetime for the same primitive. MINT-ONCE COMES FREE: _write_private_key already uses O_EXCL + 0o600 + the Windows DACL tighten and REFUSES to overwrite, so a second start loads rather than rotating. FIVE TESTS: operator cert wins and nothing is minted (the control -- it also asserts the state dir stays EMPTY); a first run mints a loadable pair; the CN names the bind host, or every verifying client fails against the cert minted for it; a second start reuses byte-identical material; and the pair survives build_api_ssl_context, which is what serve actually calls. 327 passed across six modules; ruff format, ruff check clean; mypy strict clean on all 267 source files. REMAINING ON #1276: part B (the five DEV ONLY prohibitions + pki.py's "every shipped first-party client defaults to http" prose), part C (eight http:// client defaults, one a shipped surface), the ADR 0143 supersession, and the ROTATION shape -- which is an open decision with the dispatcher: nothing re-mints an expired pair today, so on day 366 this serves a certificate every client rejects.
…erves TLS (BACKLOG #1276) The lander caught that the 1276a branch changed 41 files and touched ADR 0143 in ZERO of them. They were right, and the reason is stronger than "the ruling said to". ADR 0143 DID NOT MERELY ASSUME NO AUTO-TLS. It CONSIDERED terminating TLS on the loopback bind, named it "a full fix", and DECLINED it -- its Accepted decision rests on that decline. #1276 part A does precisely the declined thing. Landing it with 0143 untouched would put an ADR on main that reads Accepted on a premise the code has deleted: trusted, wrong, and nothing anywhere reports it. WHY SUPERSEDE RATHER THAN AMEND, and it is a measurement not a preference. 0143 declined on sizing: "an XL: it means moving the whole API to https by default and migrating every client (harness, apiclient, tray, IDE) in lockstep". Measured on origin/main, that is false: tray INFERS -- service_toml_uses_tls, ONE caller apiclient DOES NOT -- zero tls_cert_file references; it is GIVEN a URL IDE DOES NOT -- its hits are MLLP CONNECTOR schema, same name, other setting harness DOES NOT INFER, it ASSUMES -- hardcoded http://127.0.0.1:8765 One inference site plus one-line default flips. The XL that justified the decline does not exist, so the decision falls with its premise. 0143's ANALYSIS SURVIVES AND IS WHY THIS IS BETTER: its diagnosis that a secure cookie over cleartext http is dropped by Chrome and Safari and BREAKS LOGIN is correct. Its app.state.loopback mechanism goes vestigial where the engine terminates TLS -- but NOT in the tls_terminated_upstream topology, which still reaches the engine over plaintext. Recorded so nobody deletes it as dead. CARRIES BOTH OWNER RULINGS, since three halves belong to one change: always-TLS (22:19Z), 0143 superseded in the same PR, and the re-mint AUDITED not silent (22:50Z). NUMBER ALLOCATED FROM THIS WORKTREE and the index row is in this same commit, per the ledger gate's ownership rule. NOTE ON THE GATES, CORRECTED BY MEASUREMENT AFTER THE FIRST WRITING OF THIS MESSAGE. The original text claimed committing from a fresh worktree "would have failed the ledger gate, which pins 0172 to THIS worktree". THAT IS FALSE and I tested it rather than keep asserting it: this commit WAS made from a second worktree, and the ledger gate PASSES -- ledger_check.py exits 0 there, and re-running the hooks shows "ledger gate (ADR/BACKLOG number reuse) ... Passed". So ADR ownership does NOT require the committing worktree to be the allocating one; the registry lives beside the SHARED object store and every worktree of the repo sees the same allocations. Only ONE gate was ever in play here, not two. I ALSO NEARLY MISREAD THAT TEST. The cherry-pick showed no hook output, and I started to conclude the hooks had not run -- but I had piped it through `tail -12`, which cut the hook lines off the top. My instrument truncated the evidence, not the gate. The same shape cost a peer a false "backup missing" an hour ago with `tail -3`.
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.
A default-security-posture change, and the title says so on purpose. Split out of #458 by the lander so it would not land under an unrelated title.
Why this is its own PR
Both repos require zero approving reviews, so arming is merging unread and the PR title is the only durable record of what shipped. As originally stacked, this rode inside a PR titled "username identity is case-sensitive and must not depend on store collation" — a reader six months out would have had no way to learn that always-serve-TLS shipped in it.
The three rulings are one change
The Liaison's framing, and it is why the ADR is here rather than in a follow-up:
Landing any one alone leaves a document or a control that is trusted and wrong.
ADR 0172 supersedes ADR 0143's decision, and keeps its analysis
0143 did not merely assume no auto-TLS — it considered this exact change and declined it. Its own words, quoted in 0172: "A full fix — terminate TLS on the loopback bind so
effective_httpsis true." Its Accepted decision rests on that decline, so leaving it untouched would put an ADR onmainreading Accepted on a premise the code had deleted, with nothing reporting it.0172 supersedes the decision rather than amending it, because 0143's client-migration sizing is shown measurably false against
origin/main. It keeps a "What of ADR 0143 survives" section for the analysis that still holds. Thedocs/adr/README.mdindex row is in the same commit.Verified before pushing
17c16128fast-forwards33b9451a; an ADR file is in the diff — the lander's stated merge-time gate — with the same check on the pre-graft tip returning zero as the negative control. 48 ahead ofmain, 1 behind.Opened unarmed, behind #533.