Skip to content

fix(smtp): the alerts + security-event SMTP hop was encrypted but unauthenticated (#323, layer 3) - #142

Merged
wshallwshall merged 7 commits into
mainfrom
claude/repo-security-review-afbae3
Aug 2, 2026
Merged

fix(smtp): the alerts + security-event SMTP hop was encrypted but unauthenticated (#323, layer 3)#142
wshallwshall merged 7 commits into
mainfrom
claude/repo-security-review-afbae3

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Closes the third and last cell of BACKLOG #323. Layers 1–2 (PR #132) fixed the EMAIL and DIRECT
connectors; this fixes the [alerts] cell they deferred.

The defect

pipeline/alert_sinks.py::send_plain_email called smtp.starttls() with no context. smtplib's
fallback is ssl._create_stdlib_context, which is ssl._create_unverified_contextCERT_NONE,
check_hostname=False, measured on CPython 3.14.6. So every operator alert body, every per-user
security-event email
(lockout, password/roles change, new-IP admin action), and the SMTP login
credential crossed a hop that accepted any certificate presented to it.

The security-event stream is the ASVS 6.3.5/6.3.7 out-of-band channel, so an on-path attacker could
not only read those notices but suppress them — denying a user the message that their account was
just taken over.

What landed

One verifying context from the existing build_smtp_tls_context() factory, from new
[alerts].email_tls_verify / email_tls_ca_file, plumbed through three construction seams:

Seam Why it needed plumbing separately
EmailTransport ops alerts
SecurityEventNotifier a genuinely separate call site, with a weaker build condition than the alert sink — a config can produce one without the other
POST /alerts/test-email hand-rolls its own transport. Unplumbed, the operator's "test my mail server" button would exercise a different TLS posture than live alerts — the compensating-control-on-a-false-premise shape this whole item is about

Three judgement calls, stated rather than buried

1. An acknowledgment switch, not the connectors' clamp. The connectors refuse verify-off against
the clamped weakened_tls_escape_permitted_here(). That is inert here — measured three ways: this
cell is constructed in the API lifespan, outside build_check_registry's active_hop_posture scope
(the contextvar is stamped only in pipeline/wiring_runner.py), so current_hop_posture() is None
and the clamp degrades to the unclamped escape. It would have provided no refusal at all. Hence
[security].allow_unverified_alert_smtp_tls at the serve gate, shaped like ADR 0140's keyless-PHI
second ack.

2. The gate also refuses email_use_tls=false, which is broader than the item asked for.
Measured: insecure_tls_allowed() is read in alert_sinks.py only on the webhook http://
branch — cleartext alert SMTP was gated by nothing. Refusing verify-off while permitting cleartext
would hand an operator a bypass onto the strictly worse posture, so the condition is "this hop
does not authenticate the relay", true of both. Strictly adds refusals (ADR 0092 decision 5);
byte-identical on the shipped defaults.

3. security_loosenings() gained a 5th required alerts parameter and TWO entries, not one. The
deviation and the acknowledgment are different facts: under enforcement=warn an operator can run
verify-off with no acknowledgment, so keying the report on the switch alone would leave the actual
weakening invisible — the exact failure mode the registry exists to prevent. Required not optional,
per the function's own contract ("an optional parameter is a detector that silently fails to fire").
Unlike cleartext_accepted this deviation is settings-scoped, so all three call sites report it
completely.

Proof: negative control

Stash the production change, keep the tests → 7 assertions go red at assert None is not None.

That includes the pre-existing test_email_transport_sends_via_smtp, whose sent["tls"] is True
assertion stayed green for the entire insecure period — and whose fake had already been widened to
accept context= by PR #132 without the production code ever passing one. It accepted the kwarg and
threw it away. "STARTTLS was issued" is not a security assertion; the fake now records the context and
asserts CERT_REQUIRED + check_hostname + VERIFY_X509_STRICT + the TLS 1.2 floor.

Deliberately NOT built

The residual called for a shared refuse_unverified_smtp_tls() helper. It is not here, and #323 now
says why rather than leaving it a silent omission: the alerts cell refuses at the serve gate and would
never call it (zero callers), and the two connectors' refusals are not identical — measured by
ast.Raise collection with the cell name normalised, 483 vs 542 chars, Direct inserting an
S/MIME-specific harm sentence. "Extracting" them would reword a shipped operator-facing refusal and
reverse the written decision at transports/email.py:180-185 that a third spelling is how the next
bug gets written.

Also out of scope, stated not fixed: [alerts].email_smtp_port=465 has never worked on this cell
(send_plain_email has no SMTP_SSL arm). The connection-scoped reader for the connectors'
tls_verify=false belongs to #333, which stays open and gets a note saying so.

Docs

Five documents said something this makes false; one said something true that this makes false in the
opposite direction
docs/PHI.md stream 11 was the honest line describing the defect. That
asymmetry is why the doc sweep is its own commit. SECURITY-LOOSENING.md also carried a universal
("a verify-off hop keeps the clamped escape") that this falsifies; rewritten to "at least the
connector cells" per CLAUDE.md §11 rather than swapped for a new completeness claim. ADR 0029 amended
in place (ADR 0115). The "seven verifying outbound TLS hops" count is unchanged — seven counts
hops carrying a revocation guard, and this one deliberately does not.

#323 → ✅ (its two OPEN glyphs folded into one banner). #139 stays ⛔ DECLINED, with its false premise
resolved — #323 built verification; #139 asks for the anti-feature.

Verification

ruff check + ruff format --check clean · mypy strict clean (260 files) · crypto-inventory gate OK
at 61 sites with no new registration (the context is a bare local, so neither pipeline file names
ssl; the gate is bidirectional and a stale registration reds it the other way) · full local suite
10007 passed.

Two local failures, both pre-existing and environmental, neither caused by this branch:

  • test_gate_installed_parity — the installed worktree gate (3b4e3e2f) does not match the
    committed source (c6e6d700). An environment fact every session on this box hits; a stale installed
    gate is a downgrade, so it wants the owner's attention rather than a silent reinstall.
  • test_workflow_shell_syntax141/141 blocks fail with /bin/bash: C:UsersScott..., i.e. local
    Git Bash eating the backslashes out of the temp path. This branch touches no workflow file and the
    test plus its inputs are byte-identical to origin/main.

Sequenced deliberately behind PR #138: before it the Windows Tests step cap was 26:00 against a
25:51 true maximum — nine seconds — and two 26:23 passing runs have since been observed that the old
cap would have killed.

🤖 Generated with Claude Code

…uthenticated (#323, layer 3)

The last of #323's three cells. `pipeline/alert_sinks.py::send_plain_email` called `smtp.starttls()`
with no context, so smtplib fell back to `ssl._create_stdlib_context` -- which IS
`ssl._create_unverified_context` (CERT_NONE, check_hostname=False, measured on CPython 3.14.6). Every
operator alert body, every per-user security-event email, and the SMTP login credential crossed a hop
that accepted any certificate. Layers 1-2 (PR #132) fixed the EMAIL/DIRECT connectors; this fixes the
cell they deferred.

It now builds an explicit verifying context through the same `build_smtp_tls_context()` factory, from
new `[alerts].email_tls_verify` / `email_tls_ca_file`, plumbed through THREE construction seams --
`EmailTransport`, `SecurityEventNotifier` (a genuinely separate call site, not an inheritor), and the
hand-rolled transport inside `POST /alerts/test-email`. That third one matters: unplumbed, an
operator's "test my mail server" button would have exercised a different TLS posture than live alerts,
which is the compensating-control-on-a-false-premise shape this whole item is about.

AN ACKNOWLEDGMENT SWITCH, NOT THE CLAMP. The connectors refuse verify-off against the clamped
`weakened_tls_escape_permitted_here()`. That is inert here: this cell is constructed in the API
lifespan, outside `build_check_registry`'s `active_hop_posture` scope, so `current_hop_posture()` is
None and the clamp degrades to the UNCLAMPED escape -- it would have provided no refusal at all. So
the refusal is `[security].allow_unverified_alert_smtp_tls` at the serve gate, shaped like ADR 0140's
keyless-PHI second ack.

THE GATE ALSO COVERS `email_use_tls=false`, which is broader than the residual asked for. Measured:
`insecure_tls_allowed()` is read in alert_sinks.py ONLY on the webhook http:// branch, so cleartext
alert SMTP was ungated by anything. Refusing verify-off while permitting cleartext would have handed
an operator a bypass onto the strictly worse posture. Strictly adds refusals (ADR 0092 decision 5);
byte-identical on the shipped defaults.

TWO ENTRIES in `security_loosenings()`, not one, and a 5th REQUIRED `alerts` parameter to carry them.
The deviation and the acknowledgment are different facts: under `enforcement=warn` an operator can run
verify-off with no acknowledgment at all, so keying the report on the switch alone would leave the
actual weakening invisible -- the exact failure mode the registry exists to prevent. Unlike
`cleartext_accepted` this is settings-scoped, so all three call sites report it completely. Required
rather than optional per the function's own contract: "an optional parameter is a detector that
silently fails to fire".

NEGATIVE CONTROL. Stash the production change, keep the tests: 7 assertions go red at
`assert None is not None`. That includes the pre-existing `test_email_transport_sends_via_smtp`, whose
`sent["tls"] is True` assertion stayed green for the entire insecure period -- and whose fake had
ALREADY been widened to accept `context=` by PR #132 without the production code passing one. "STARTTLS
was issued" is not a security assertion; the fake now records the context and asserts CERT_REQUIRED +
check_hostname + VERIFY_X509_STRICT + the TLS 1.2 floor.

Verified: ruff check + ruff format --check clean; mypy strict clean (260 files); crypto-inventory gate
OK at 61 sites with no new registration -- the context is a bare local, so neither pipeline file names
`ssl` (the gate is bidirectional and a stale registration reds it the other way).

NOT BUILT, deliberately: the residual's `refuse_unverified_smtp_tls()` helper. The alerts cell refuses
at the serve gate and would never call it, and the two connectors' inline refusals are NOT identical
(measured: 483 vs 542 normalized chars -- Direct inserts an S/MIME-specific harm sentence), so
"extracting" would reword a shipped operator-facing refusal and reverse the written decision at
transports/email.py:180-185 that a third spelling is how the next bug gets written.
…at it verifies (#323, layer 3)

Five documents said something about this hop that the fix makes false, and ONE said something true
that the fix makes false in the opposite direction. That asymmetry is the whole reason this is its own
commit -- a sweep that only added verification claims would have left the honest line behind, still
describing a defect that no longer exists.

docs/PHI.md stream 11 was the one accurate line in the repo about this hop: it stated the
no-SSL-context defect plainly and correctly warned readers not to generalise it to the connectors. It
now states the verifying posture, and KEEPS the half that is still true -- there is still no hop
gradient or attestation on this path, because the cell is constructed outside the active_hop_posture
scope; that is precisely why it is governed by an acknowledgment switch instead. Stream 12 inherited
"its caveat" by reference; it now names what it inherits and says explicitly that security_notify.py
is a separate call site plumbed in its own right, not an implicit inheritor.

docs/SECURITY-LOOSENING.md carried a UNIVERSAL that this change falsifies: "a verify-off hop ... keeps
the clamped MEFOR_ALLOW_INSECURE_TLS escape". The alerts cell is the first verify-off hop governed by
a [security] acknowledgment instead. Rewritten to "at least the connector verify-off cells", per
CLAUDE.md 11 -- prefer "at least" to an enumeration -- rather than swapping one completeness claim for
another.

docs/DEPLOYMENT.md's "every other verifying TLS hop is ungated" table was correct to omit this hop
while it was not a verifying hop at all. It becomes an ungated verifying hop, so it joins the table,
with the reason it deliberately carries no RevocationHopGuard: a guard here could not read the
instance posture. The "seven verifying outbound TLS hops" count at three sites is UNCHANGED and was
left alone -- seven counts hops carrying a revocation guard, and this one does not.

ADR 0029 D3 said the connector took "the same posture send_plain_email already takes". That was true
when both passed no context, which is exactly what made it a defect; PR #132 made it false; this makes
it true again for a different reason. Amended in place rather than silently re-satisfied (ADR 0115: a
secure posture cannot change without its owning feature ADR amended in the same work).

CONFIGURATION.md gains the two [alerts] rows and the [security] acknowledgment row; ASVS 5.3 gains the
verification sentence on both alerts rows, which had read as if every SMTP hop verified once the
sibling connector rows started asserting it.

BACKLOG is deliberately NOT in this commit -- a live session holds uncommitted changes there.
…ger false (layer 3)

#323 -> DONE. One banner, one glyph: the item carried TWO open glyphs (a 'Status' and a 'Filed'
line), so flipping the first to a CLOSED glyph while the second survived would have produced exactly
the CLOSED+OPEN coexistence the ledger gate rejects. Folded into a single banner. Verified with the
repo's own check (tests/test_backlog_status_check.py, 15 passed), not with my own eyeball regex --
the ad-hoc scan I wrote first agreed, but agreement between two instruments I chose is not evidence.

FOUR pieces of this item's own text were stale or self-contradictory and are fixed rather than left:

  - The "What" call-site table described five live `starttls()` sites. All five are fixed; four had
    been fixed by PR #132 and the table was never updated, so it was already 4/5 wrong. Marked
    historical rather than deleted -- it is the record of what was found.
  - It quoted docs/PHI.md VERBATIM ("PR #1163 hardened the EMAIL message destination connector...").
    That sentence had already been deleted from PHI.md by PR #132. A verbatim quote of text that no
    longer exists is the least detectable kind of doc rot, so the correction says so explicitly.
  - Step 5 asserted the three SMTP fakes "all declare `def starttls(self) -> None:` with no context
    parameter". Also already false: PR #132 widened all three, INCLUDING the alerts one, without the
    alerts production code passing a context -- so the fakes accepted the kwarg and discarded it. The
    assertion half was the part that mattered, and the item now says that.
  - The "Migration risk / breaking change" paragraph is DELETED. It physically contradicted the
    retraction four paragraphs above it, which records the owner confirming on 2026-08-01 that there
    are no existing deployments. Two contradictory paragraphs in one item is the self-contradiction
    shape #323 itself calls out in #139.

#139 STAYS DECLINED -- the glyph does not move. Its premise was false and its own correction block
said so; that block now records the resolution. The distinction matters and is written out: #323 built
VERIFICATION, #139 asks for the ANTI-FEATURE. The capability now exists as `email_tls_verify=false`,
but deliberately not in the shape #139 wanted -- instance-wide, a named loosening, and refused on an
enforcing PHI instance without the acknowledgment. Its "nearest existing mechanism" paragraph also
claimed the global MEFOR_ALLOW_INSECURE_TLS escape governed this cell; measured, it never did (that
escape is read only on the webhook http:// branch), so cleartext alert SMTP was gated by nothing until
layer 3.

#333 gets a note and KEEPS its open banner. #323 delivered the registration SHAPE it asks for and a
worked precedent, but neither of its two deviations is closed and the completeness floor is still
blind to per-connection and [alerts] fields. Written as "copy this example", not as progress.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 2, 2026 13:17
…g re-derived from red CI

All four of these were established in session traffic while building #323 layer 3, and none of them
was written anywhere durable. The next person to add a TLS call site would re-derive the first one the
expensive way -- a red leg on all three OS matrices -- which is exactly what #323's own opening commit
e4728d7 did by adding `import ssl` to transports/{email,direct}.py with no inventory entry.

  1. The gate is BIDIRECTIONAL. "Just register the file" is not a free fix: an unregistered file that
     imports a trigger fails one way, and a REGISTERED file that stops importing it fails the other.
     A registration is a standing commitment.
  2. `if TYPE_CHECKING: import ssl` does NOT hide the import under deferred annotations.
  3. The only real escape is not naming the type -- hold the inputs as plain data and let one
     inventoried builder produce the context into a bare local with no annotation.
  4. For SMTP that builder already exists (tls_policy.build_smtp_tls_context), which is why layer 3
     added zero inventory entries while adding a verifying context to a third SMTP cell.

Docstring only -- no behaviour change. Gate re-run after the edit: OK, 61 sites, no drift; the four
scanner/doc/static twins still pass (73).

Credit: the adr-0154 session flagged e4728d7 from CI history and then made the argument for writing
it down rather than leaving it in two transient transcripts.
wshallwshall added a commit that referenced this pull request Aug 2, 2026
…ide (#144)

Follow-on to #143, which merged before this measurement existed. Adds the one
argument #340 was missing, and it is a better one than the cycle-count case.

Measured 2026-08-02T13:41Z, re-derived here rather than relayed:

  open=14  armed=9  armed_and_inert=6  armed_and_CLEAN=0
  #142 BEHIND  #139 BEHIND  #128 BEHIND  #101 BEHIND  #96 BEHIND  #71 DIRTY

Two-thirds of the armed PRs in this repo cannot land, and NOT ONE armed PR was
CLEAN. #71 is armed and DIRTY, so it can never land at all.

Why this belongs in the item: everything else in #340 is an efficiency argument,
and an efficiency argument has a "then be patient" answer. This one does not.
Every session here reads autoMergeRequest != null as "this will land" -- I said
exactly that about my own PR an hour before measuring this -- when for six of nine
it means "this waits until a human runs gh pr update-branch", with nothing
reporting the difference. That is the ADR 0158 defect class (a green signal that
means nothing) caught live rather than in retrospect.

ADR 0158 is referenced by number, not linked: it is not on main yet.

The measurement came out of the sandbox-codec session's queue claim, checked by the
announce-hook session, and the connection to 0158's class is sandbox-codec's. Both
routed it to me rather than writing it, since #340 is claimed here. Re-derived
independently before writing; their figures and mine agree exactly.
wshallwshall added a commit that referenced this pull request Aug 2, 2026
…per-CI-run figure

Reframed after the session that owns PR #142 reproduced the diagnosis independently.
Three substantive corrections, none of them cosmetic:

1. FRAMING. The banner led with the flake; it now leads with what is actually wrong.
   The substring check fails when encryption worked AND would pass on a weak encoding
   that happened to avoid those three characters. The second half is the PHI defect;
   the flake is only what made someone look.

2. RATE. 5.5e-4 is per assertion per leg. There are two such assertions and three OS
   legs (ubuntu + windows-2022 + windows-2025, verified against ci.yml), so the figure
   an operator experiences is 1 in 303 full CI runs, not 1 in 1,820. Both over-estimate
   caveats kept: L is from one measured value, and the hex-fingerprint prefix is not
   base64.

3. SCOPE. Reversed my own recommendation to sweep the >=6-char sites for shape.
   Rewriting a dozen correct assertions costs review attention for no risk reduction;
   the pattern-propagation concern is answered by writing the ">=6 characters" rule
   into the :49-58 convention comment instead. Fix list is now :95, :303, and
   test_content_search.py:123 (4 chars, unsafe under that same rule -- an addition to
   the reviewing session's list, in its own framing).

Also records the confirmation-by-prediction: #142's re-run came back 25 passed with
the prediction written beforehand, so a green re-run confirms a chance collision
rather than resetting the question.
wshallwshall added a commit that referenced this pull request Aug 2, 2026
The correction I added said 093db33 (#132) left alert_sinks.py as "the only
remaining instance on main". That is a checklist-shaped claim with an expiry
date: BACKLOG #323 layer 3 (PR #142) closes the alerts call site, and the
sentence goes false the moment it lands. Dating the observation does not help a
reader who greps for it in a month and finds nothing.

Restated as what happened rather than what is currently true -- #132 closed the
two connectors, the alerts call site is tracked as #323 layer 3 -- so it holds
whether or not #142 merges, and it says outright that the current state must be
grepped rather than cited from here.

Deliberately does NOT assert that #142 closed the cell: #142 is open at time of
writing, and asserting a merge that has not happened is the same defect pointing
the other way.

found by: the repo-security-review session, which owns #142 and re-derived all
three call sites against origin/main before raising it.
wshallwshall added a commit that referenced this pull request Aug 2, 2026
…just adopted

The #142-owning session pointed out that :512 is safe for a reason unlike the
others -- ':' is not in the base64 alphabet -- and suggested it as a third safety
category. It is a real category and it is now ground (1), stated more usefully than
either of us first had it: a token containing a character the value cannot contain
is a PROOF at any length, and it is the same principle that makes the recommended
fix (assert ADT not in raw) deterministic. Rule and remedy are now one idea.

But the argument as given does not survive, and I checked before adopting it. The
haystack is <marker>:<base64>, NOT base64 alone, and the marker carries colons --
so ':' IS representable and ground (1) does not cover :512. It is deterministic for
a different reason: fixed marker layout with the version field reading v1, plus a
body with no ':' for the run to straddle. Structure, not alphabet. The item now says
so explicitly, because a rule that is right about the conclusion and wrong about the
mechanism is the thing this whole item is about.

Verified mechanically rather than argued: over the haystack's actual character set,
the ADT fixture carries \r & . \ ^ | (unrepresentable -> ground 1 genuinely holds),
while DOE, JANE, SECRET, WESTWING, SECRETSTATEMRN and ':v2:' are ALL fully
representable -- so ground (1) applies to none of them and length is their only
defense. That is what the table already claimed; now it is checked.

Also records why the float64 trap survives review, from #344's owner: the naive
expression is correct everywhere you would sanity-check it and silently wrong only
in the tail.
@wshallwshall
wshallwshall merged commit 45a5f82 into main Aug 2, 2026
33 checks passed
@wshallwshall
wshallwshall deleted the claude/repo-security-review-afbae3 branch August 2, 2026 16:35
wshallwshall added a commit that referenced this pull request Aug 2, 2026
…ason (#147)

* backlog(#347): PHI-at-rest tests assert short-substring absence against base64 ciphertext

`tests/test_store_encryption.py:95` asserts `"DOE" not in raw` against a value
encrypted under a fresh random key, so the base64 body is fresh random text every
run. Measured p ~ 5e-4 per run per leg; it fired on PR #142's windows-2022 py3.14
leg with encryption working correctly.

Filed rather than fixed: the fix direction is the maintainer's call (decoded-bytes
assertion vs. non-recoverability vs. full-plaintext absence), and simply widening or
deleting the substring check would drop the PHI-at-rest property it reaches for.

Includes the sibling audit: the identical 3-char shape survives at :303, a 4-char
instance at test_content_search.py:123, and ~13 >=6-char instances whose rate is
immaterial but whose shape is the same. `test_off_by_default_stores_plaintext` does
NOT share the shape (deterministic equality) and needs no change.

* backlog(#347): lead with the instrument, and correct the rate to the per-CI-run figure

Reframed after the session that owns PR #142 reproduced the diagnosis independently.
Three substantive corrections, none of them cosmetic:

1. FRAMING. The banner led with the flake; it now leads with what is actually wrong.
   The substring check fails when encryption worked AND would pass on a weak encoding
   that happened to avoid those three characters. The second half is the PHI defect;
   the flake is only what made someone look.

2. RATE. 5.5e-4 is per assertion per leg. There are two such assertions and three OS
   legs (ubuntu + windows-2022 + windows-2025, verified against ci.yml), so the figure
   an operator experiences is 1 in 303 full CI runs, not 1 in 1,820. Both over-estimate
   caveats kept: L is from one measured value, and the hex-fingerprint prefix is not
   base64.

3. SCOPE. Reversed my own recommendation to sweep the >=6-char sites for shape.
   Rewriting a dozen correct assertions costs review attention for no risk reduction;
   the pattern-propagation concern is answered by writing the ">=6 characters" rule
   into the :49-58 convention comment instead. Fix list is now :95, :303, and
   test_content_search.py:123 (4 chars, unsafe under that same rule -- an addition to
   the reviewing session's list, in its own framing).

Also records the confirmation-by-prediction: #142's re-run came back 25 passed with
the prediction written beforehand, so a green re-run confirms a chance collision
rather than resetting the question.

* backlog(#347): exact rates, a verified count, and the float64 zero that hid in the analysis

Three sessions reviewed this; each correction below is theirs, verified here rather
than adopted.

RATES. Recomputed exactly: 1 in 1,821 per assertion per leg, 1 in 911 per leg, 1 in
304 per full CI run (I had floored two of them). JANE at test_content_search.py:123
is 8.58e-6 = 1 in 116,509.

THE ZERO. The 14-char row was written as "p=0 (unreachable)". It is 7.44e-24. Cause
reproduced: 1-(1-64**-14)**144 UNDERFLOWS to exactly 0.0 in float64, silently, with
no warning, in a column of plausible values -- inside an analysis arguing that token
length is the discriminator, at the one row where length breaks the arithmetic. The
item now carries the trap, the exact value, and the idiom that does not underflow
(-expm1(N*log1p(-x))). Every figure recomputed both ways and agreeing.

THE COUNT. Three different numbers were quoted before anyone checked (7, then 5, then
~16 for a different denominator). Re-derived from the tree: 7 lines / 8 clauses against
ciphertext in test_store_encryption.py, 2 of them unsafe; ~16 repo-wide. The item now
states the basis AND the exclusions (:905-908 and :927-928 are caplog assertions
against log text, not ciphertext; :56/:532/:625 are full-plaintext; :512 is
deterministic twice over) so the count is not re-litigated a fourth time.

#344 CITATION. Kept -- its owner confirmed the framing and supplied the discriminator
that stops a reader folding the two: this one would fire at exactly the same rate on
an infinitely fast machine. Related line now says what the citation is NOT.

#346 / ADR 0158. Added as the closer sibling: an assertion that passes for a reason
unrelated to the property it tests. ADR 0158 cited WITHOUT a link -- I guessed its
filename, checked, and was wrong; the file is on PR #145's branch, not on main.

VERIFICATION. backlog_status_check.py falsified against this item: a deliberately
doubled banner makes it fail at BACKLOG.md:8429 naming #347. The first probe attempt
silently no-op'd on a cp1252 decode and "passed" -- the same shape this item is about.

* backlog(#347): two safety grounds, and a correction to the one I had just adopted

The #142-owning session pointed out that :512 is safe for a reason unlike the
others -- ':' is not in the base64 alphabet -- and suggested it as a third safety
category. It is a real category and it is now ground (1), stated more usefully than
either of us first had it: a token containing a character the value cannot contain
is a PROOF at any length, and it is the same principle that makes the recommended
fix (assert ADT not in raw) deterministic. Rule and remedy are now one idea.

But the argument as given does not survive, and I checked before adopting it. The
haystack is <marker>:<base64>, NOT base64 alone, and the marker carries colons --
so ':' IS representable and ground (1) does not cover :512. It is deterministic for
a different reason: fixed marker layout with the version field reading v1, plus a
body with no ':' for the run to straddle. Structure, not alphabet. The item now says
so explicitly, because a rule that is right about the conclusion and wrong about the
mechanism is the thing this whole item is about.

Verified mechanically rather than argued: over the haystack's actual character set,
the ADT fixture carries \r & . \ ^ | (unrepresentable -> ground 1 genuinely holds),
while DOE, JANE, SECRET, WESTWING, SECRETSTATEMRN and ':v2:' are ALL fully
representable -- so ground (1) applies to none of them and length is their only
defense. That is what the table already claimed; now it is checked.

Also records why the float64 trap survives review, from #344's owner: the naive
expression is correct everywhere you would sanity-check it and silently wrong only
in the tail.

* backlog(#347): cite ADR 0158 by its rule and its class, not by its number

The session landing ADR 0158 (PR #145) confirmed #347 is a genuine instance and
supplied the precise anchor instead of a general pointer. Verified against the ADR
on its branch before citing -- all three lines read verbatim as quoted:

  :246  "An equality check satisfiable by coincidence is not an equality check."
  :60   Class 2 -- a control that cannot OBSERVE or ACT ON its own failure
  :61   Test: "if this control were broken, what would tell me?"
  :56 / :439  the taxonomy explicitly disclaims completeness

Citing the RULE is what makes the reference survive renumbering, and the Class 2
test is the sharper statement of this defect than anything I had written: if the
encryption were replaced tomorrow with a weak encoding, "DOE" not in raw would
still go green. The answer to "what would tell me" is the control itself.

Still deliberately unlinked -- 0158 is on #145's branch, absent from main, so a
relative link renders broken. The follow-up (file this against 0158 once it is on
main) is recorded as NOT done here, with the reason: that session declined to add
instances its author did not choose, and padding a rescued document at merge time
is its own defect. Their call, recorded so it does not read as an oversight.

* backlog(#347): itemise the provenance, and source the one number that was not mine

Three fixes, all of the same defect the item is about -- an observational claim
carrying more confidence than its sourcing supports.

1. THE 200k SIMULATION IS NOT MINE. It arrived with the originating defect report
   and I never ran it; the sentence read as though this filing corroborated the
   rate that way. Every word was accurate, which is the shape: an unsourced
   observational claim inside a sentence whose whole job is telling the reader how
   much to trust the number beside it. Now attributed, with this filing's actual
   contribution (exact Fraction derivation, cross-checked against expm1/log1p)
   stated separately.

2. "PRODUCED INDEPENDENTLY BY THREE SESSIONS" was an aggregate confidence claim.
   Two sessions derived rates; the third contributed process discipline. Replaced
   with itemised attribution -- who supplied the framing, the >=6 rule, the
   discriminator, the demand to falsify the gate -- and an explicit statement that
   no claim rests on a count of who agreed. A session count is not evidence.

3. "#344 IS a fixed bound meeting variable latency" -> "#344's THESIS is".
   That item's instance 2 has since been re-diagnosed as a swallowed lock-timeout
   (SET LOCK_TIMEOUT 0 -> native 1222 caught and returned as a normal empty, with
   the dispatcher then parking in a terminal IDLE) -- not a bound at all. The
   wholesale characterisation was over-broad, and the item now says not to lean on
   "#344 = timeouts" as a premise.

The chain that prompted this went two sessions -> one -> none -> mechanism-only
-> mechanism-only-labelled-as-deduction, on a separate claim, every step a
good-faith correction, and the conclusion correct throughout. Only the stated
mechanism was hollow, and the stated mechanism is what the next reader carries.

* backlog(#347): require the replacement assertion to be falsified before it is trusted

The item told an implementer HOW to fix the assertion but not how to know the fix
works. Shipping the replacement on an unfalsified green would reproduce the defect
inside the remedy -- a green taken as evidence for a property it cannot see is the
whole item.

So the fix direction now closes by requiring the deliberate break: hand the store
an IdentityCipher or plant a plaintext body, watch the rewritten test go RED, then
restore.

With the trap that makes it more than a formality, from the session that settled
#344's instance 2 today: proving the INSTRUMENT can fire is only half -- the
WORKLOAD must also be able to produce the failure class. Their 800-iteration repro
loop returned 800/800 green against a live SQL Server while hunting a lock-
contention bug, because running the two tests in isolation was the one
configuration that could not generate contention. They had falsified the probe and
not the rig, which felt like all of it. A rig that excludes the condition it hunts
reports silence, and silence reads like evidence.

Merged origin/main first (PR #148 / BACKLOG #346 landed): clean auto-merge, no
conflict this time, verified by CONTENT and not only by count -- 271 items, #345,
#346 and #347 all present, and all five of #347's late revisions still resolving in
the merged file.
wshallwshall added a commit that referenced this pull request Aug 2, 2026
…ready pointed at (#145)

* feat(coord): announce yourself to the other sessions in this repo

Every coordination control in this repo is PULL-based: a new session discovers
its peers from the SessionStart banner and the peers learn nothing until someone
trips the collision gate. That is too late for the collision that costs the most
-- two sessions building the same THING in different files, where nothing
file-shaped can catch it. This closes the push direction.

It ASKS, it cannot send. Hooks are shell commands and session messaging is MCP,
so the hook prints the instruction, the live peer roster and the id-resolution
rule at the first prompt that has intent to report; the model does the sending.

UserPromptSubmit, not SessionStart: at SessionStart a session knows it exists and
nothing else, so it can only say hello -- the interrupt without the information.

THE ID RULE IS THE PAYLOAD, and it is counter-intuitive enough that the text
states it with its evidence. The registry id in this repo's banners is NOT the
MCP session id; measured, a registry id and an MCP id for one session shared no
characters. Branch does not join them either -- the two rosters reported
different branches for the same checkout in 2 of 6 cases. Only cwd joins, and it
must be matched EXACTLY: every worktree cwd is an extension of the primary's, so
a prefix match resolves a peer in the primary to an arbitrary worktree session.
A registry id passed to send_message fails SILENTLY, which reads as the peer
ignoring you.

EVERY DECISION LEAVES A RECEIPT, because the bug being fixed was a hook that was
wired, fired, resolved nothing and exited 0 for weeks -- byte-identical to a
healthy hook with no peers. For the same reason the shim carries its OWN
missing-script notice: every receipt the hook writes lives INSIDE the script,
strictly downstream of the resolution failure that IS the bug, so the shim is the
one surface that still reports when the script does not resolve. It is gated on
presence.ps1 so the entry stays silent in every unrelated repo on the machine.

It always exits 0 -- a UserPromptSubmit hook that fails can block the user's
prompt. It consumes presence.ps1 and therefore the single liveness fence; it does
not invent a second notion of live. A separate 'mefor-announce' marker keeps it
outside install-coordination's mefor-coord strip and outside the website repo's
mefor-web-announce entry in the same settings file, so no installer can delete
another's hook, and -Only UserPromptSubmit -Uninstall removes announce alone
without disarming the collision gate.

* test(coord): pin the announce hook, and the anti-no-op wiring class

Most tests for a hook like this assert an ABSENCE, and a hook that does nothing
at all satisfies every one of them -- which is precisely the production failure
being fixed. So the silence assertions are paired with a positive arm: two tests
run the SAME runner against fixtures differing only in whether a peer exists, and
if the silence tests ever start passing for the wrong reason the positive one goes
red first.

test_announce_wiring.py is the class the repo had no test for AT ALL: does the
thing that gets INSTALLED reach a script that EXISTS, and does it say so when it
does not? Its absence is exactly how a wired-but-inert shim survived for weeks.
test_every_wired_script_exists_in_this_checkout was written FIRST and watched
fail, naming the missing script and printing all three paths it scanned; a green
gate is only evidence if it was shown it can see the failure.

Also pinned, each because it was got wrong somewhere first:

- The foreign UserPromptSubmit entries -- another repo's shim and an unmarked
  waiting-flag cleanup -- survive install AND uninstall byte-identical. That is
  the only thing standing between a one-line wiring edit and deleting a hook this
  repo does not own.
- A peer with no StartedAt ranks LAST, not first. ConvertFrom-Json coerces
  ISO-8601 to DateTime while the '' fallback stays String; Sort-Object over that
  mixed column raises ZERO errors and puts the empty string FIRST, so without an
  explicit projected key the least-trustworthy row silently takes the top of a
  capped target list.
- NO_SESSION_ID and DISABLED write their receipt with NO injected -StateDir. An
  earlier draft resolved the state dir after those branches, so the receipt was
  unwritable in production while a test that always injected one went green.
- Self is excluded by BOTH nets independently: a roster that cannot tell you from
  a sibling makes the session message itself.
- Hostile peer text cannot escape the peer-data block or emit a non-ASCII byte,
  a hostile session id cannot escape the state dir, and two ids that sanitise
  identically get two markers.
- Two concurrent runs announce exactly once. session-context.ps1 is registered
  twice on this box today, so double firing is a live pattern, not a hypothetical.

* docs(coord): document announcing yourself, and correct a false claim about .claude

WORKTREES.md gains the "Announcing yourself" section that the hook's own emitted
text and the shim's missing-script notice both cite by name, so the pointer has to
land on main in the same merge. It states the id rule ONCE, as the source of
record: registry id is not the MCP id, cwd is the only join key and must be
matched exactly rather than by prefix, a usable id starts with local_, and a wrong
one fails silently.

It also states what the change does NOT do. There is no receive-side hook, so the
rule that an announcement is peer DATA -- not an operator instruction, and not
something to reply to -- lives in the prose and in the fixed message shape and
nowhere else. Reachability is given honestly: presence.ps1 is authoritative for
who EXISTS, list_sessions only for who can be MESSAGED, and measured, they
disagreed 6-to-1. Cost is stated rather than left to be discovered.

CORRECTION, and it is why this doc change is in scope rather than deferred: the
same chapter claimed ".claude/settings.json is tracked (shared across worktrees)".
It is not. /.claude/ is git-ignored, and git ls-files .claude/ returns nothing --
so a worktree's copy is a creation-time snapshot nothing refreshes and several
siblings have none at all. That sentence sat at the exact point a reader decides
where to install a hook, and it argues for the wrong answer; the new section
directly contradicted it.

SESSION-DRIFT-CONTROLS.md records announce as the only PUSH control in the D4
layer, plus the two new guarantees worth tracking separately: that wiring reaches
a script that exists, and that a resolution failure is now reported by the shim.

* fix(coord): stop the collision gate blocking files a peer committed and finished

Reported by another session with a repro: it committed a file, went clean, said
in writing it was done and handed the file over -- and the peer it handed off to
was still refused the edit.

overlap.ps1's `Files` is the UNION of what a branch COMMITTED-and-not-yet-landed
with what is dirty in its tree. The gate denied on any live row in that set, so
"this branch authored it" was treated as "someone is typing in it right now".
Those are different claims. The first stays true for the branch's whole life;
only the second is what the gate exists to detect.

It self-clears on merge -- overlap already intersects three-dot with two-dot so a
LANDED branch stops claiming its files. But nothing clears it before landing, and
with PRs currently unable to merge, "until it lands" is indefinite: the blocked
set grows monotonically and is never released. Two sessions that coordinated
correctly and explicitly still cannot hand a file over. That is precisely the
failure this gate's own docstring names -- a gate that cries wolf gets
uninstalled.

overlap.ps1 already told callers to treat its signals differently ("block on
live, mention dormant"), but no caller COULD: the row unioned the two signals
away. So the row now carries `Dirty`, and the single-file query sets
`MatchedDirty` saying which signal actually matched.

The gate now DENIES only on an uncommitted edit in a live worktree, and REPORTS
committed-and-clean as context instead -- the peer may already have done what you
are about to do, which is worth knowing and not worth refusing over.

Fails SAFE across the upgrade: a cached row predating `MatchedDirty` has no such
property and is treated as dirty, so the gate degrades to its previous
over-blocking rather than silently permitting a real collision.

Also, while in the file: `git status` now runs with --no-optional-locks. A plain
status REWRITES the index of the repo it inspects, and this walks every peer
worktree -- so merely asking "what is in flight" was mutating other sessions'
checkouts.

Verified against the live repro and both directions: the reported file now
allows with context; a file with uncommitted changes in a live worktree still
denies; an untouched file stays silent.

* feat(coord): lead the announce roster with the claim note, not the worktree name

Reported by the session it happened to: its worktree is named
inter-session-communication-*, auto-generated at creation from a task that
session has never worked on -- it has been doing ASVS scorecard work for its
entire life. The directory name is the most visible identifier in presence.ps1,
overlap.ps1 and this hook's output, and it had already misled TWO sessions
(including this one) into guessing that session was building the announce hook.

A worktree name is a creation-time label, not a statement of current work, and
nothing keeps the two in sync. The claim note is the only field written
DELIBERATELY to say what a session is doing, so the roster now prints it, and the
legend tells the reader to prefer it over the name.

Joined on the claim's `worktree` path, normalised the same way as every other cwd
key here. Fail-open throughout: no claims directory, an unreadable claim, or a
peer with no claim all just mean the name is the only thing we have -- which is
exactly the status quo, never an error.

Same session also flagged that the branch I read for it from list_sessions was
stale (a spent, merged branch). The announce text already refuses to join on
branch and says why; this is a second, independent reason not to trust it.

* docs(coord): name the silent-control defect class in the drift inventory

A control that cannot distinguish 'ran and resolved' from 'ran and found nothing'
is not installed, however it looks. The announce shim outlived every other
silent-control defect found the same day BECAUSE it printed a status message --
which is more convincing than silence.

The structural cause is the reusable part: every receipt that hook would have
written lived inside the script the shim failed to find, so every check sat
strictly downstream of the failure it existed to detect. Looking was not
neglected, it was impossible. The question to ask of a new control is which
surface still reports when the control itself fails to load.

Formulation owed to a peer session that hit four instances of this class in one
day and named it more sharply than I had.

* docs(coord): record the broadcast constraints six sessions learned the hard way

Announce-on-join introduces a session; it does not let an established one push an
operational notice. That increment is deferred, and on 2026-08-01 six sessions
rehearsed it by hand for four hours. Three constraints fell out, recorded so the
next attempt does not rediscover them:

- A broadcast needs an EXPIRY or a predicate the RECIPIENT can evaluate, never a
  promise from the sender. A merge freeze shipped with 'lift when #119 merges';
  #119 died on an unrelated CI timeout, so five sessions held on a condition that
  could not arrive and a second round was needed to retract it.
- 'Don't do X' is the wrong primitive when automation already has X armed. The
  freeze asked for restraint while six PRs had auto-merge ARMED and would have
  landed with nobody clicking anything. The right ask was an action: disarm.
- Coordination a tool cannot read does not count. Two sessions agreed IN WRITING
  to hand over a file and the gate still refused, because the agreement was prose
  and the gate reads git.

Field data from the sessions that lived it, not speculation.

* test(coord): pin overlap's dirty-vs-committed signals against real git

Nothing drove overlap.ps1's row computation against a real repository, so the
question "does MatchedDirty hold when a file is dirty AND committed at once" was
unanswerable by the suite. Raised by the session that spent an evening in exactly
that state.

THAT CASE IS THE ONE THAT FAILS SILENT, which is why it gets a real fixture
rather than a stub row. A peer with uncommitted edits in one region and landed
work in another is a genuine collision. Had MatchedDirty been derived from the
committed diff instead of the working tree it would read FALSE there, the gate
would allow, and two sessions would write one file with nothing reported. The
over-block this replaced was loud and annoying; that would be quiet and cost
someone their work.

Verified the tests can SEE it rather than assuming: sabotaged the row to publish
an empty Dirty set -- the precise mis-implementation warned about -- and both
MatchedDirty assertions went red; restored, all five green. A test written after
the code, never observed failing, is a test of nothing.

Also pins that overlap does not rewrite a peer worktree's git index, by comparing
the index mtime across two queries. An observer must not perturb what it
observes, and this one was doing so on every PreToolUse before f55d6c6.

Stub rows would only have asserted that the plumbing carries a value someone else
computed; the whole question here is what git actually reports.

* test(coord): assert a wired coordination hook resolves to a script that exists

Raised by the session that traced the shim: the coordination hooks are not
installed copies, they are inline commands that locate their script in a working
tree at every invocation. If neither base yields the file, Test-Path fails, the
loop ends, nothing runs, and the tool call proceeds with no hook and no signal.
"The hook is uninstalled" and "the hook ran and permitted this" are
indistinguishable from outside, and nothing was watching.

Not hypothetical: a foreign UserPromptSubmit entry sat in this same settings file
for weeks probing a script that exists only in another repo.

The risk composes badly for collision_gate.ps1 specifically, which now (a) fails
OPEN on any error, (b) denies less by design after the dirty-vs-committed split,
and (c) silently no-ops when unresolvable. Individually defensible; together the
realistic bad day is "the gate was never running and nobody noticed". This closes
(c) -- the observation is not mine, and it is a good one.

Found immediately on writing it: FIVE user settings files across account
directories, not the one I knew about. The informational test also prints the
original defect as output rather than leaving it invisible:
  FOREIGN UserPromptSubmit [mefor-web-announce] -> scripts/hooks/announce.ps1:
  RESOLVES NOTHING HERE
It is another repo's entry, so this reports it and does not touch it.

Carries a NEGATIVE CONTROL, because the assertion passed on the first run and a
green that has never been shown to fail is not evidence. The real hooks cannot be
unwired to prove the predicate works -- the primary checkout is shared with live
sessions -- so it is exercised against a path known not to exist.

Local-machine only: CI has no user settings and these skip there, which means CI
does NOT guard this property. Said plainly, and every test prints what it scanned
BEFORE it can skip, per test_gate_installed_parity.py -- the pytest config has no
-rs, so a skip would otherwise render as a bare dot with no reason.

* docs(adr): ADR 0158 silent controls, plus a session handoff

Session ended on an owner stop-work instruction at 96% weekly account usage, so
this lands the two things that would otherwise have existed only in a transcript.

ADR 0158 records a defect class that recurred at least a dozen times across
independent surfaces in one working day, in at least two sub-classes: a bound
stated independently of the thing it bounds, and a control that cannot observe or
act on its own failure. Its spine is that a signal carrying too little
information to act on makes every reader re-derive significance by hand until one
of them derives it wrong -- so a correct-but-useless RED costs what a silent green
costs.

EVERY FIGURE IN IT WAS RE-DERIVED BY SOMEONE WHO DID NOT PRODUCE IT, against the
repository and the GitHub API. That pass refuted six claims, including four CI
numbers that were already merged, and including corrections this session had
itself issued hours earlier. Seven retractions are recorded INSIDE the document,
each carrying a found-by tag -- because the central empirical finding is that no
retraction was made by the author of the claim it retracts, and that is invisible
if attribution is smoothed into one voice.

Shape over detection is reported as a ratio rather than flattered: three fixes are
covered by tests in required CI legs, two by tests that always skip in CI, one by
a workflow change with a live residual, and the rest are corrected prose or still
open. The Decision separates ENFORCED rules, each naming its gate, from CONVENTION
that is knowingly re-breakable.

The handoff records what is pushed, what is filed-not-built, and the traps -- a
linked worktree's .git being a FILE, a Windows Python unable to read MSYS paths, a
raw hasher giving a false mismatch against a git blob on CRLF, and claim.ps1
silently discarding a note refresh. Each is stated as a fact plus its measurement.

It also records, first, the five claims this session got wrong -- including
retracting a CORRECT estimate on the strength of an incorrect measurement, and
sending that false claim to four sessions and the correction to only three.

One more arrived while committing this: the leak gate rejected the handoff for a
branch slug, on a line a standalone run of the same scanner had passed. The hook
scans STAGED files; the standalone run scanned tracked ones. Two scopes, one tool,
and only the fail-closed gate could see it. Recorded in the handoff.

No engine behaviour changes.

* docs(adr): land ADR 0158 -- silent controls, green signals that mean nothing

ADR 0158 was authored and committed in 994bfb1 on
claude/intersession-communication-hooks-a52335, a trailing commit pushed about an
hour and a half AFTER that branch's PR (#133) had already squash-merged. It
therefore never reached main and no PR carried it, while the coordination ledger
had already allocated the number: docs/adr/README.md stopped at 0156 and 0158 was
taken, so the index pointed at a document that did not exist.

That gap had a cost. At least four sessions cited this silent-controls taxonomy as
"ADR 0157" -- an unrelated HA demotion-safety document allocated to another
worktree and still in flight on PR #139. The document that settles the citation
was the one sitting unmerged.

This branch is cut from 994bfb1 itself, so the original commit stays in history
and authorship is exact. The prose, voice and ASCII-only convention are its
author's. This commit drops the session handoff and makes three factual
corrections where main moved underneath the branch after it was written, each
tagged inline in the ADR's own update convention rather than silently rewritten:

  * 0fdc326 is unreachable from main (this repo squash-merges). It is now given
    as "merged as 851c849 (#130)", matching the mapping the ADR already uses for
    7ebb2ff/2a6649fb.
  * transports/email.py and transports/direct.py were cited as carrying the same
    bare starttls() call. 093db33 (#132) gave both an explicit verifying context;
    pipeline/alert_sinks.py:384 is now the only remaining instance.
  * The "the false sentence is still there" claim (five sites, one of them
    numbered Decision rule 13) is closed out: on main the clause survives only
    inside its own CORRECTED block at :5270 and as a quotation at :7476. The
    interval is recorded; the rule it produced is unchanged.

HANDOFF-announce-hook.md from 994bfb1 is deliberately not landed: it is session
state rather than project documentation, no root HANDOFF-*.md has ever existed on
main, and it would publish local shim mechanics into a public repo. It stays on
its own branch.

Verified: exactly one commit in the repository ever added a 0158 ADR and exactly
one 0158 filename exists across all refs, so nothing competes for the number. The
index row is unchanged from 994bfb1 and appears exactly once.

No engine behaviour changes.

* docs(adr): make the 0158 TLS update non-perishable

The correction I added said 093db33 (#132) left alert_sinks.py as "the only
remaining instance on main". That is a checklist-shaped claim with an expiry
date: BACKLOG #323 layer 3 (PR #142) closes the alerts call site, and the
sentence goes false the moment it lands. Dating the observation does not help a
reader who greps for it in a month and finds nothing.

Restated as what happened rather than what is currently true -- #132 closed the
two connectors, the alerts call site is tracked as #323 layer 3 -- so it holds
whether or not #142 merges, and it says outright that the current state must be
grepped rather than cited from here.

Deliberately does NOT assert that #142 closed the cell: #142 is open at time of
writing, and asserting a merge that has not happened is the same defect pointing
the other way.

found by: the repo-security-review session, which owns #142 and re-derived all
three call sites against origin/main before raising it.

* docs(adr-0158): replace rotting line-number citations with greppable strings

The document's own rule, applied to itself: a quoted string survives a file
edit, a line number does not. Ten citations replaced.

WHY NOW. All three ci.yml citations (:229, :233, :254) resolve to unrelated
text the moment #138 lands, and six docs/BACKLOG.md citations had ALREADY
rotted on main before that -- +14 to +40 lines of drift from #345/#346/#347
being appended, with every cited claim surviving verbatim at a new address.
Measured fresh against origin/main and against #138's branch, not reused from
the report that found them.

TENSE, not just addresses. Two of the quoted strings do not survive #138 --
"Measured over the 11 PASSING windows-2025 runs" and "1.46x" are both deleted
by it, because #138 ADOPTS this ADR's retractions 1-3 wholesale (12:31, 21:34,
25:51, 1.006x, 1.206x, pools 42/39/36). Left in the present tense those two
sentences would ship knowingly false the hour #138 merges, so they now say
what ci.yml stated when this was written. The retractions themselves are
unchanged and are vindicated by #138, not contradicted.

ANCHORS ARE SINGLE-LINE ON PURPOSE. A first pass rewrapped two quotes across a
newline, which makes them ungreppable and would have swapped one rot for
another. Every anchor is now verified to grep as one line AND to resolve in
the tree it points at -- "ZERO tests failing" resolves in ci.yml both on main
and after #138.

pyproject.toml:266 was simply wrong: the zizmor pin is at :271, in the group
opening at :268. Replaced with the group name, which is what the sentence
needed and cannot rot. The residual it reports -- that the pin's home is
outside zizmor's paths filter -- is verified TRUE and unchanged.

OUT OF SCOPE, deliberately: line numbers into less volatile files remain
(test_stage_dispatcher.py, claim.ps1, zizmor.yml, install-coordination.ps1,
freethread-smoke.yml, collision_gate.ps1). So the ADR does not yet "state no
line numbers" outright -- see the handoff note.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant