Skip to content

feat(coord): announce across accounts, not just the local CCD instance - #1149

Open
wshallwshall wants to merge 1 commit into
mainfrom
claude/self-announcement-hook-d3193c
Open

wshallwshall wants to merge 1 commit into
mainfrom
claude/self-announcement-hook-d3193c

Conversation

@wshallwshall

@wshallwshall wshallwshall commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

What was wrong

announce-session.ps1 could only reach peers the model's ccd_session_mgmt send_message tool can address, which is one login and one surface. Everything else rendered as SKIP and got nothing.

Measured on this repo 2026-09-14:

Count
Live peers 10
Reachable by MCP (list_sessions returned exactly these) 3
Learned nothing 7

scripts/coord/mail.ps1 was built for exactly those two cases and names them both in its own header. The hook contained zero references to it. The two components shipped separately and were never wired.

What this does

Section 16b mails the unreachable half directly, so coverage goes from 3 of 10 to 10 of 10. The split is by what each lane can reach: a prompt for peers the model can address, an action for peers it cannot, because no instruction can conjure a session id outside the caller's namespace.

It runs before the NO_PEERS exit. The new-peer set is computed from messageable peers alone, so an all-cross-login fleet would otherwise take that exit having announced nothing.

Design points, each paid for by a measurement:

  • Shells out to mail.ps1, never writes the inbox file. mail.ps1:324 says its caps are advisory because anyone who can write the file never runs that code; the enforcing copy at mail-drain.ps1:216-236 truncates where mail.ps1 refuses. Bypassing the sender trades a visible refusal for a silent mid-sentence cut.
  • Wraps to 90 columns and hard-cuts unbreakable tokens. The receiver refuses any line over 240 chars. A peer measured an ordinary 370-char intent line refusing all 7 of its sends. Word wrap alone is insufficient: one long path breaches the cap by itself.
  • Intent comes from the claim note, never the prompt. The hook has the prompt in its payload. Delivery copies a body into a peer transcript nothing here can delete, and mail.ps1 forbids message content outright.
  • Three bounds: 3 per prompt, 12 per session, 6000 ms wall clock. Only the clock survives disk contention; a count cap cannot bound time. UserPromptSubmit has a 15 s timeout and its failure mode is a blocked user prompt. One send costs 0.76-1.32 s, mean 0.98 over three timed spawns, and the peer lookup has already spent ~1.0 s.
  • 120-minute TTL, not the 4320-minute default. A stale announce would hold a drain slot for three days against a 5-per-injection cap.
  • Unattended peers excluded. Their filter is flagged UNEXERCISED at the top of the hook, and mail's staleness argument applies.

-AsLogin is added so a hand-run can exercise the login filter. Without it -SelfTest cannot resolve its own login, the filter goes off, and it reports an upper bound as a measurement: the same live roster read reachable=9 without it and reachable=3 with it.

Why the tests are the interesting part

The suite went green without them. $MailScript defaults to the real mail.ps1, which refuses the fixtures' fake D:\t\... recipients, so every send failed, every mailable peer fell back to SKIP, and the existing SKIP assertions kept passing while the feature did nothing at all.

14 tests now drive the lane through an injected stub. Two form a controlled pair on one peer, differing only in the mail script, and the verb flips MAILED against SKIP — so a green result discriminates a working lane from an inert one.

Checks

Check Result
pytest tests/test_announce_hook.py tests/test_announce_wiring.py 82 passed in 138 s
ruff check / ruff format Clean
Hook source ASCII-only Verified, max byte 126
Pre-commit (12 gates incl. leak guard, ledger, secrets) All passed
mypy Not implicated; nothing under messagefoundry/ changed

CI has since been read, and this paragraph originally named the wrong leg. Corrected here rather than in a comment, so the retraction sits at the claim.

  • windows service smoke is skipped for this change by the detect server-DB + docker changes path filter. It was never going to produce a result, because nothing here touches server, DB, or docker paths. Naming it as a leg to read was wrong.
  • The leg that actually exercises this change is repo harness tests (windows-2025). tests/test_announce_hook.py is in tests/tooling_manifest.txt, and the test legs deselect that tier with -m 'not tooling'. Result: 3225 passed, 17 skipped, 8 xfailed in 933 s, and the tier's own "Prove the tier actually ran" step reported tooling tests EXECUTED: 3225.
  • The durations report names tests/test_announce_hook.py:: tests, so the module was not skipped by its pwsh/os.name gate. That gate is module-level, so the 14 new tests ran under it and the leg is green.

PR state at head 155cf1eb4: mergeable=MERGEABLE, mergeStateStatus=CLEAN, 7 workflow runs success and 1 correctly skipped.

One operational note

The installed shim resolves the hook from the git common dir's parent first and breaks on that hit, so every session in every worktree runs the primary checkout's copy. Nothing picks this up from a branch, which is correct but means the only real-fleet test is post-merge.

announce-session.ps1 could only reach peers the model's ccd_session_mgmt
send_message tool can address, which is one login and one surface. Measured on
this repo 2026-09-14: 10 live peers, 3 reachable, list_sessions returning
exactly the 3 on this login, so 7 sessions learned nothing. mail.ps1 was built
for exactly those two cases and names them both in its own header, and this hook
contained zero references to it.

Section 16b mails the unreachable half directly, and runs BEFORE the NO_PEERS
exit so an all-cross-login fleet is not silent: the new-peer set is computed
from messageable peers alone, so that exit would otherwise fire having announced
nothing. Coverage goes from 3 of 10 to 10 of 10.

- Shells out to mail.ps1 rather than writing the inbox file. mail.ps1:324 says
  its caps are advisory because anyone who can write the file never runs that
  code, and the enforcing copy in mail-drain.ps1:216-236 TRUNCATES where
  mail.ps1 REFUSES. Bypassing the sender trades a visible refusal for a silent
  mid-sentence cut at the far end.
- Wraps the body to 90 columns and hard-cuts unbreakable tokens. The receiver
  refuses any line over 240 chars, and an ordinary 370-char intent line was
  measured refusing all 7 of a peer's sends. Word wrapping alone is not enough:
  one long path or branch name breaches the cap by itself.
- Intent comes from the claim note, never from the user's prompt. The hook has
  the prompt in its payload; delivery copies a body into a peer transcript that
  nothing here can delete, and mail.ps1 forbids message content outright. No
  note is reported as NOT DECLARED rather than filled in from the worktree name.
- Three bounds: 3 sends per prompt, 12 per session, 6000 ms of wall clock. Only
  the clock survives disk contention from several sessions draining one
  directory; a count cap cannot bound time. UserPromptSubmit has a 15 s timeout
  and its failure mode is a blocked user prompt, not degraded coordination. One
  send costs 0.76-1.32 s, mean 0.98 over three timed spawns.
- 120-minute TTL, not mail.ps1's 4320-minute default. A stale announce is worth
  nothing and would hold a drain slot for three days against a 5-per-injection
  cap.
- Unattended peers are excluded. Their filter is flagged UNEXERCISED at the top
  of the hook, and mail's own staleness argument cuts against waking a scheduled
  run with an announce it cannot act on.

Adds -AsLogin so a hand-run can exercise the login filter. Without it -SelfTest
cannot resolve its own login, so the filter goes off and the diagnostic reports
an upper bound as a measurement: the same live roster read reachable=9 without
it and reachable=3 with it, and only the second matches list_sessions.

Receipts go to v=2 and carry a mail= field beside msg=, so a reader can tell a
v1 receipt's absent mail count from a v2 receipt's zero. New outcome code
ANNOUNCED_MAIL covers a successful cross-account announce with no messageable
peer in the fleet, which a bare NO_PEERS reported as nothing happening.

14 tests drive the lane through an injected stub, and they exist because the
suite went green without them. $MailScript defaults to the real mail.ps1, which
refuses the fixtures' fake D:\t\... recipient paths, so every send failed, every
mailable peer fell back to SKIP, and the existing SKIP assertions kept passing
while the new feature did nothing at all. Two of the new tests form a controlled
pair on one peer, differing only in the mail script, and the verb flips MAILED
against SKIP.

Verified: 82 passed (test_announce_hook.py + test_announce_wiring.py), ruff
check and format clean, hook source still ASCII-only. windows-service-smoke and
the rest of CI run only on a hosted runner and have not been read.
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

LANDER review: BLOCKING on one line, and I am deliberately escalating it above the severity my reviewer gave it. Read against head 155cf1e.

THE GUARD BETWEEN A USER PROMPT AND AN UNDELETABLE TRANSCRIPT PASSES WHEN NOTHING WAS SENT.

def body_lines(log: Path) -> list[str]:
    if not log.exists():
        return []
    ...

assert not any("do a thing" in ln for ln in body_lines(log)), body_lines(log)

If the stub log does not exist, body_lines returns [], any(...) over an empty list is False, and the assertion passes. So this test is green in three different worlds: the hook correctly omitted the prompt, the hook mailed nothing at all, or the stub never ran. It cannot tell them apart.

WHY I AM RAISING IT FROM A NOTE. Its own docstring states the stakes better than I can: "mail.ps1's header forbids message content in a body, and delivery copies the body into the recipient's transcript, which nothing in this repo can delete. The hook HAS the prompt in its payload and must never mail it."

Three properties compound here and any one alone would only be a note:

The failure is IRREVERSIBLE. A prompt that reaches a peer's transcript cannot be recalled by anything in this repository.

The payload is arbitrary user text, which in this product routinely includes pasted HL7. That makes it a PHI path, not merely a privacy one.

And this is the ONLY guard. Nothing else in the suite stands between the payload and the wire.

A vacuous assertion on a reversible property is a note. A vacuous assertion that is the sole control on an irreversible PHI path is the control failing silently, which is the defect this repository has spent the night finding in other people's code.

THE FIX IS ONE LINE, asserting the instrument fired before asserting what it saw:

captured = body_lines(log)
assert captured, "no body was captured -- this guard examined nothing"
assert not any("do a thing" in ln for ln in captured), captured

That makes a hook that mails nothing fail the test loudly instead of passing it quietly, and costs nothing when the hook is working.

THE REST OF THE DIFF IS SOUND AND I WANT TO BE CLEAR ABOUT THAT. The second delivery lane is well built: it runs before the NO_PEERS exit so an all-cross-login fleet is no longer silent, bounds the fan-out three ways, versions the receipt, and marks mailed peers in the roster. Two larger concerns were raised against it -- that a queue accept is recorded as delivery, and that a dead mail lane is indistinguishable on stdout from a healthy one -- and BOTH were refuted 3 of 3 on adversarial verification. I am recording them as considered and dismissed so they are not rediscovered as new.

THREE NOTES, none blocking:

The -SelfTest mail split filters on Cwd and the two Reason patterns but omits the already-mailed exclusion that section 16b applies. The comment two lines above says "Same predicate as that section; if the two ever diverge the diagnostic is lying about the feature" -- as written they diverge, so the diagnostic over-reports mailable peers.

Write-Receipt's comment states "Never a full home path -- counts only", and the new note=$mailNote carries mail.ps1's thrown text verbatim, whose single-recipient refusal embeds the peer's absolute cwd. Enough of it survives the 80-character clean to break the stated invariant.

docs/WORKTREES.md:938 says the receiver refuses lines over 240 characters; the receiver truncates and the SENDER refuses. Line 936 states it correctly, so the section contradicts itself.

Lift the block on the one-line test fix and this is a merge.

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