Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/branch-leak-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
run: |
if [ -n "$MEFOR_FORBIDDEN_TOKENS" ]; then
export MEFOR_REQUIRE_TOKENS=1
export MEFOR_MIN_DETECTORS=names=7,estate=13,site_prefixes=1
export MEFOR_MIN_DETECTORS=names=8,estate=14,site_prefixes=2
echo "token list loaded from the MEFOR_FORBIDDEN_TOKENS secret (fail-closed, per-section floor)."
else
# A push event cannot come from a fork, so unlike security.yml there is no legitimate
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ jobs:
# the env var first, so that file would never be read -- it would only drop the full real
# token list into the job workspace for every later step to see.
export MEFOR_REQUIRE_TOKENS=1
export MEFOR_MIN_DETECTORS=names=7,estate=13,site_prefixes=1
export MEFOR_MIN_DETECTORS=names=8,estate=14,site_prefixes=2
echo "token list loaded from the MEFOR_FORBIDDEN_TOKENS secret (fail-closed, per-section floor)."
elif [ "$IS_FORK_PR" = "true" ]; then
echo "fork PR -- the secret is unavailable BY DESIGN; structural-only scan."
Expand Down
51 changes: 50 additions & 1 deletion docs/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13914,7 +13914,18 @@ point, which are the parts that must survive it.**

## 1302. mail.ps1 accepts an MCP-namespace session id in -ToSessionId and the message becomes silently undeliverable, expiring with neither sender nor recipient told

> 🔢 **Re-scored 2026-08-20 -> P2.** Value **5/10** · Difficulty **2/10** · _fill-in_. The validation gap stands -- mail.ps1:106 takes -ToSessionId unvalidated, :240 stores it, and mail-drain.ps1:852 string-compares it against the harness id read at :490, so a wrong-namespace id is filtered on every pass -- but the item's reporting claim does not: mail-drain.ps1:825-836 writes an 'expired-unshown' receipt on the sweep, mail.ps1:484-487 already tells the sender to read exactly that file and what the disposition means, and mail-drain.ps1:1014 reports the filtered count to the recipient on every drain. Value 5 rather than the filed 7 or the scorer's 6 because the item's value argument was built on those instruments being absent and all three ship, leaving a detection DELAY and an ambiguous no-receipt reading rather than a silent loss, on developer coordination with no product or PHI axis. Difficulty 2 and arguably generous: mail.ps1:153 already dot-sources mail-claim.ps1, whose Test-SessionId at :209 is the exact UUID shape check the fix needs, so the remainder is a post-binding refusal plus a must-not-trip arm for the no-sessionId broadcast case. _(was 7/10 · 2/10.)_
> ✅ **SHIPPED 2026-08-21 -- `mail.ps1` now REFUSES a `-ToSessionId` that is not a harness session id, at SEND, before any message is written. The sender is the only party who can correct the id, and was the only party never told.** The drain already reported its half (*"N message(s) are addressed to a different session id and were left in the inbox"*); the send path printed `Queued 1 message(s)` and nothing else. **That asymmetry was the defect, and it is why the guard sits on the send side rather than in the drain -- the drain's filter is CORRECT: a worktree outlives its occupant, so an id-addressed note must not reach a stranger.**

> **THE SHAPE.** A harness session id is a bare UUID (the drain reads `$hook.session_id`); the MCP namespace prefixes its own as `local_<uuid>`. Two id spaces for one session, compared with `-ne` at [`mail-drain.ps1:852`](../scripts/hooks/mail-drain.ps1), so a wrong-namespace id never matches and the message waits in the inbox until it is swept to `expired/` -- silently in both directions.

> **MEASURED, NOT HYPOTHETICAL.** Six messages from one seat -- a level report, a CI mechanism diagnosis, two unprompted self-retractions and a request to pull two never-started items -- stranded for a whole session while the recipient read that lane as silent and wrote "level unreported" three times. They were found only by opening the box by hand.

> **PARTIAL CONTROL, RECORDED RATHER THAN DISCOVERED LATER.** This catches a wrong-NAMESPACE id. It does **not** catch a correctly-shaped but **STALE** one -- an id belonging to a session that has ended fails identically and just as silently. A pass at send is not a promise of delivery, and nothing here should be read as one.

> **THE MUST-NOT-TRIP ARM IS ASSERTED IN THE SAME TEST AS THE REFUSAL**, so a guard that rejected everything could not pass by satisfying one half: a message with NO `-ToSessionId` is the ordinary broadcast and still sends, and a genuine bare-UUID id still sends. **RED-FIRST IN BOTH DIRECTIONS:** disabling the guard reddens the refusal test, and widening it to reject everything reddens the must-not-trip test.

> **Re-scored 2026-08-20 -> P2.** Value **5/10** · Difficulty **2/10** · _fill-in_. The validation gap stands -- mail.ps1:106 takes -ToSessionId unvalidated, :240 stores it, and mail-drain.ps1:852 string-compares it against the harness id read at :490, so a wrong-namespace id is filtered on every pass -- but the item's reporting claim does not: mail-drain.ps1:825-836 writes an 'expired-unshown' receipt on the sweep, mail.ps1:484-487 already tells the sender to read exactly that file and what the disposition means, and mail-drain.ps1:1014 reports the filtered count to the recipient on every drain. Value 5 rather than the filed 7 or the scorer's 6 because the item's value argument was built on those instruments being absent and all three ship, leaving a detection DELAY and an ambiguous no-receipt reading rather than a silent loss, on developer coordination with no product or PHI axis. Difficulty 2 and arguably generous: mail.ps1:153 already dot-sources mail-claim.ps1, whose Test-SessionId at :209 is the exact UUID shape check the fix needs, so the remainder is a post-binding refusal plus a must-not-trip arm for the no-sessionId broadcast case. _(was 7/10 · 2/10.)_

>
> **Filed 2026-08-21 -- not started. The one place in this transport where a message is genuinely LOST rather than late, and both ends read it as delivered.** `scripts/coord/mail.ps1:106` declares `-ToSessionId` as a bare `[string]` with **no validation**, and `:240` writes it into the message as `sessionId`. The drain then compares that value against the **harness** session id -- `scripts/hooks/mail-drain.ps1:852`, against `$sessionId` sourced at `:490` from `$hook.session_id`. **Three id namespaces exist for one session -- registry, MCP and harness -- and only one of them can ever match.**
> **THE MECHANISM, verified in code rather than inferred from the symptom.** An MCP id is `local_`-prefixed; a harness id is a bare UUID. The comparison at `:852` is a string inequality, so a `local_` id can never equal the value it is tested against, on any drain, ever. The message is skipped every pass, stays in `inbox/`, and expires. **Nothing errors at send time, nothing errors at drain time, and nothing reports the expiry to either party.** `docs/WORKTREES.md` already records that a registry id and an MCP id for one session **shared no characters** -- so the namespaces are known to be disjoint, and nothing acts on that knowledge at the point where it matters.
Expand Down Expand Up @@ -16453,6 +16464,8 @@ declared serialised write order on that file. Building this alongside those with
first risks the exact same-file collision this project's own collision-detection conventions exist to
catch.

---

## 1357. The connscale FD probe cannot see a multi-process engine: PID-set re-resolution never runs

> 🔢 **Filed 2026-08-25 - not started.** ***A single probe tick costs LONGER THAN THE ENTIRE MEASUREMENT WINDOW, so the PID set is resolved once per sweep step and never re-resolved -- and a multi-process engine's worker children are invisible to the FD gauge.*** **Measured: one tick costs 1419-1848 ms against `hold_seconds = 1.5`.** So `_RESOLVE_EVERY_TICKS = 8` ([`probe.py:60`](../harness/load/connscale/probe.py)) is **unreachable by construction** -- not a badly-chosen constant, a constant the window cannot afford at any cadence. Its own comment reasons at "the runner's poll cadence", which this profile does not have.
Expand Down Expand Up @@ -16490,6 +16503,42 @@ catch.

---

## 1360. three test files pick a free pid and rely on it staying free, so a loaded runner reuses it and a DEAD record reads as a veto

> ✅ **SHIPPED 2026-08-26 (lander, authored per ADR 0165 -- the underlying fix is the builder's own
> verified work, landed unmodified; this banner is mine).** Three copies of a `_find_free_pid` helper
> (`test_worktree_prune_merged.py`, `test_coord_presence.py`, `test_session_registry.py`) each spawned
> `cmd /c exit`, waited for it to exit, slept, and returned its pid as "free" -- free at the moment it
> returns, and nothing keeps it free afterward. Replaced by one `tests/_dead_pid.py`, returning
> `2147483647` (`Int32.MaxValue`): within the `[int]` cast `Test-RecordLiveness` performs, non-zero so
> it takes the liveness path rather than the `UNREADABLE` shortcut, and structurally unassignable on
> either platform (Linux caps pids at ~2^22, Windows pids are multiples of 4 far below 2^31) -- dead by
> construction rather than by timing. Verified independently against the landed diff before writing
> this banner: `tests/_dead_pid.py` exists, defines `NEVER_LIVE_PID: Final = 2147483647`, and all
> three test files import it.
>
> **Diagnosed from a real failure**, not from inspection alone: `windows-2025` run `32268545492`,
> `tests/test_worktree_prune_merged.py:753`, `assert d["Occupants"] == []`, an ordinary exit-1
> assertion beside an unrelated crash in the same run. The comment on the original helper --
> `time.sleep(0.3) # let the OS reap it before we claim the pid is gone` -- states the intended
> guard and the mechanism does the opposite: reaping RELEASES a pid for reuse rather than reserving
> it, so the sleep widens the window it appears to close.
>
> **The fix does not stub, mock, or force the liveness verdict** -- the real `Get-Process` call still
> runs and still returns "not running" on its own; only the pid handed to it is now unassignable. The
> constraint that shaped this over the obvious alternative: the tests assert both `Occupants == []`
> and `Decision == "SKIP"`, so any remedy that short-circuited the verdict would pass while testing
> nothing.

**Cluster:** CI reliability / test determinism. **Priority:** P2. **Verdict:** build.
**Severity:** no product effect, no PHI effect, no deployment axis (sec. 0) -- test-suite determinism
only. The cost was a required context redding on a race whose failure looked like a real occupancy
veto.

---

---

## 1363. the retirement flag is preempted by the key-set guard, so a full-list retirement cannot be expressed

> 🔢 **Filed 2026-08-26 (lander) - not started. Successor to [#1307](#1307-the-safe-asvs-writer-has-no-path-to-retire-an-anchor), whose banner is amended rather than reopened.** Value **5/10** · Difficulty **3/10** · _fill-in_. #1307 shipped `--allow-retirement` and the flag genuinely works for a PARTIAL retirement. It is unreachable for a FULL-LIST one, and that is the shape both authorised retirements take -- so the sanctioned outcome #1307 exists to make expressible still cannot be expressed for the case that prompted it.
Expand Down
34 changes: 33 additions & 1 deletion harness/load/multishard.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ class EngineAttribution:
inbound_rows: int # number of inbound (source) connection rows this engine reports
foreign_rows: int # inbound rows whose name does NOT carry this engine's tag (a steal ⇒ > 0)
reads: int # Σ inbound read across this engine's own rows
#: The engine's OWN reason for each lane it reports as not-listening, verbatim from `/connections`
#: (`error`, which the API sets from `connection_failed()` per ADR 0031). Empty on a clean run.
#:
#: CARRIED BECAUSE `reads == 0` CANNOT DIAGNOSE ITSELF WITHOUT IT, and the engine already knows.
#: `inbound_rows` and `foreign_rows` are CONFIG-derived, not traffic-derived -- the API appends a
#: source row for every registry inbound unconditionally and `read` is always an int, never None
#: (`api/app.py`, the `/connections` builder), so both pass unchanged on an engine that received
#: NOTHING, including one whose listeners never bound. The test's own docstring says as much: the
#: isolation proof "is config-derived so it holds regardless of the write lock". So when `reads`
#: reads 0 the other two counters say nothing about why, and the failure message was left naming a
#: number with no cause attached.
#:
#: Measured: occupying one engine's inbound ports with a listen-never-accept squatter reproduces
#: the exact CI triple -- inbound_rows PASS, foreign_rows PASS, reads 0 FAIL -- and the engine
#: reported `status: "failed"` on both lanes throughout. The information was present and discarded.
failed_lanes: tuple[str, ...] = ()


@dataclass(frozen=True)
Expand Down Expand Up @@ -195,6 +211,9 @@ def to_json_dict(self) -> dict[str, object]:
"inbound_rows": e.inbound_rows,
"foreign_rows": e.foreign_rows,
"reads": e.reads,
# In the artifact as well as the assertion: a CI reader who has only the uploaded
# JSON must be able to attribute a zero-read engine without re-running anything.
"failed_lanes": list(e.failed_lanes),
}
for e in self.per_engine
],
Expand Down Expand Up @@ -693,6 +712,7 @@ def _attribute_engines_sync(
# empty attribution — the smoke asserts positive rows, so it won't silently pass.
out.append(EngineAttribution(node.node_id, tag, 0, 0, 0))
continue
failed: list[str] = []
for row in rows:
if row.read is None: # inbound (source) rows carry a read counter; skip outbound rows
continue
Expand All @@ -701,7 +721,19 @@ def _attribute_engines_sync(
reads += row.read
else:
foreign_rows += 1
out.append(EngineAttribution(node.node_id, tag, inbound_rows, foreign_rows, reads))
# Collected for EVERY inbound row, not only this engine's own: a lane that failed to bind
# under a peer's tag is exactly as diagnostic, and filtering by tag here would drop the
# cross-engine case the isolation assertion above exists to catch.
# DIRECT ATTRIBUTE ACCESS, NOT `getattr(row, "error", None)`. `EngineClient.connections()`
# is typed `list[ConnectionRow]` and that model declares `error`, so the field is
# guaranteed and a default would only ever mask a RENAME -- after which this would report
# "no failed lanes" forever, silently, on exactly the runs it exists to explain. A
# diagnostic field that fails closed to "nothing to report" is worse than no field.
if row.error:
failed.append(f"{row.name}: {row.error}")
out.append(
EngineAttribution(node.node_id, tag, inbound_rows, foreign_rows, reads, tuple(failed))
)
return out


Expand Down
34 changes: 34 additions & 0 deletions scripts/coord/mail.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,40 @@ if ($Send) {
}
}

# BACKLOG #1302 -- FAIL THE SENDER, WHO CAN FIX IT, RATHER THAN THE RECIPIENT, WHO CANNOT.
#
# A `-ToSessionId` from the wrong namespace is compared literally against the reading session's
# harness id (`mail-drain.ps1`: `[string]$m.to.sessionId -ne $sessionId`), never matches, and the
# message sits in the inbox until it is swept to expired/. MEASURED: six messages from one seat --
# a level report, a CI mechanism diagnosis, two unprompted self-retractions and a request to pull
# two items -- stranded for a whole session while the recipient read that lane as silent. The send
# path printed `Queued 1 message(s)` for every one of them.
#
# THE ASYMMETRY IS THE DEFECT, and it is why this check goes HERE. The drain ALREADY reports its
# side ("N message(s) are addressed to a different session id and were left in the inbox"), so the
# recipient is told. The SENDER is told nothing, and the sender is the only party who can correct
# the id.
#
# THE SHAPE: a harness session id is a bare UUID (the drain reads `$hook.session_id`). The MCP
# namespace prefixes its own (`local_<uuid>`), and that is exactly the shape that stranded them --
# two id spaces for one session, compared with `-ne`.
#
# PARTIAL CONTROL, AND RECORDING THAT IS PART OF THE FIX. This catches a wrong-NAMESPACE id. It
# does NOT catch a correctly-shaped but STALE one -- an id belonging to a session that has since
# ended fails identically and just as silently. A pass here is not a promise of delivery.
#
# MUST NOT TRIP ON THE ORDINARY CASE: no `-ToSessionId` at all is the normal broadcast, and it has
# to keep delivering untouched. The guard is scoped to a value the caller actually supplied.
if ($ToSessionId -and $ToSessionId -notmatch '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
throw (
"-ToSessionId '$ToSessionId' is not a harness session id, so the drain would compare it " +
"against the reading session's id, never match, and leave the message in the inbox until " +
"it expired -- with neither end told. A harness session id is a bare UUID; an id carrying " +
"a namespace prefix such as 'local_' belongs to a different id space. Send with -To " +
"<worktree path> and omit -ToSessionId unless you have the harness id."
)
}

# PER-TARGET, NOT ALL-OR-NOTHING. Now that a publish can genuinely fail -- the verify is real, so a
# move that did not happen is reported instead of assumed -- a broadcast that aborted on target 1
# would hide targets 2..N, and one that swallowed the failure would put the defect back at the
Expand Down
Loading
Loading