Skip to content

feat(coord): durable per-seat episode records + a reader that refuses to lie - #399

Merged
wshallwshall merged 5 commits into
mainfrom
claude/session-tracking-handoffs-9740d2
Aug 14, 2026
Merged

feat(coord): durable per-seat episode records + a reader that refuses to lie#399
wshallwshall merged 5 commits into
mainfrom
claude/session-tracking-handoffs-9740d2

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Durable per-seat episode records plus a reader that refuses to lie, so a session under a different
account
can cold-start and rebuild the fleet.

scripts/coord/seat.ps1            new
scripts/coord/fleet.ps1           new
scripts/hooks/seat-record.ps1     new
scripts/coord/install-coordination.ps1   +1 wiring row, +1 shim builder, +1 marker

4 files, 1072 insertions, ZERO deletions. Nothing existing is touched.

(A two-dot diff against this branch reports a fifth file with 58 deletions. That is main moving
forward under a branch one commit behind -- #393 touched that test -- not an edit here. Three-dot,
and git log -- <path> on the branch, both confirm it was never touched.)

The design choice worth reading, because it is a refusal to inherit a silent failure

The Stop-hook shim resolves against the primary checkout first and falls back to the current worktree,
so until this is on main, every other seat's recorder resolves nothing.

The author deliberately did not use the standard shim for that case. Shim='std' emits zero
bytes and exit 0
when it cannot resolve -- byte-identical to a healthy hook with nothing to do.
The fleet would have looked quiet and green while recording nothing, with every settings.json
passing. They demonstrated that failure at 0 bytes / exit 0 before choosing. Theirs speaks instead.

That is the same class this repo has been hit by all day: a null that a broken instrument and a
healthy one print identically.

Not installed, deliberately

Wiring a user-global hook across five config roots is the owner's call and the last step, not the
first
. This PR lands the scripts; it installs nothing.

The leak gate caught a slug and it was fixed, not allowlisted

The first attempt was blocked on an internal branch slug hardcoded in a comment. Removed the slug
rather than adding an exemption
-- clean on re-run, and re-verified here over all 1072 added lines
before pushing to a public repo.

A finding that belongs in the record, not just the design doc

git stash create captures TRACKED edits only -- there is no -u. So a file that exists nowhere
but a working directory -- the single highest-value thing any rescue could capture -- is exactly
what a stash does not cover
. The author found this because it silently failed to capture the two
scripts they were writing at the time.

The record now separates tracked from untracked, and names untracked files as held by no git object
anywhere
. Unpushed work is the thing that dies when a seat goes quiet; untracked work is a
strictly worse case of it, and it is invisible to the mechanism most likely to be trusted for rescue.

…at refuses to lie

Cold-start recovery across an account switch. When a pool is exhausted the owner
opens ONE session under another account; it must reconstruct what every seat was
doing from disk alone, with no inherited context, memory or realtime channel.

seat.ps1 writes one episode record per (worktree, session) under
mefor-coord/seats/. Involuntary: -Record derives branch, tip, merge-base, touched
paths, unpushed count, dirty state and claim/allocation attribution from git and
the hook payload. A seat registry was rejected twice on this project, and one
ground -- voluntary declaration decays -- is correct and measured, so the
hook-written half needs no discipline and -Declare only adds intent on top.

fleet.ps1 projects. Nothing is stored as a verdict, because a verdict is read
after the world moved; every judgement is recomputed at read time. The receipt
states what was EXAMINED, not what was found, and STOP conditions suppress the
roster rather than render a confident empty answer.

Three defects found by running it, each fixed here:

- A worktree path outlives its occupant. The first record written inherited 18
  backlog allocations from a session eight days earlier in the same directory.
  Claims and allocations now carry this-episode vs worktree-inherited, compared
  against the episode start, and the briefing tells a replacement not to rehome
  the inherited ones.

- ConvertFrom-Json parses an ISO string into a DateTime whose string cast drops
  the Z, so parsing it back applied the local offset and ages rendered five hours
  in the future. Re-reading the pattern finds nothing; the type was wrong.

- git stash create captures TRACKED edits only, so the highest-value thing to
  rescue -- a file that exists nowhere but that working directory -- was silently
  uncovered. Measured against these two scripts, which it would have lost. The
  record now separates tracked from untracked and the briefing names untracked
  files as held by no git object anywhere.

Design, 29 adversarial attacks and residual risk:
mefor-coord/handoffs/2026-08-14-fleet-continuity-cold-start-DESIGN.md
…ting shim

Stop only. Stop already pays a pwsh spawn for the mail drain, and the installer
measured roughly 366ms per spawn against about 19 tool calls per turn -- a second
UserPromptSubmit hook would tax every prompt for a path exercised at
account-switch frequency. Stop still bounds the record to the last COMPLETED turn.

NOT Shim=std, and that is the point. The std shim is Test-Path-then-run: with the
script absent it emits zero bytes and exits 0, byte-identical to a healthy hook
with nothing to do. Demonstrated here at 0 bytes / exit 0. The installer already
records that class as how a wired-but-resolving-nothing hook survived for weeks.

It matters more for this hook than for any existing one. The shim resolves against
the PRIMARY checkout, which tracks main and lags it. Until this lands on main every
seat resolves nothing -- and under the std shim the fleet would look quiet and
healthy while recording nothing, in every session, with every settings.json green.
So the shim speaks when it cannot resolve, gated on the checkout actually being
this project so unrelated repos on the same machine stay quiet.

Fifth marker, mefor-seat. Pairwise non-containment against the four existing
markers was re-checked rather than assumed, and the check was validated by a
positive control on mefor-mail / mefor-mail-urgent, the exact pair the installer
names as the mistake to avoid. Separate marker so the recorder can be removed
without disarming the mail drain, which shares the Stop event.

Verified: hook exits 0, advances the record, picks up session_id, kind and
entrypoint from the payload, and emits zero bytes on the success path -- Stop
output is shown to the model, and a line every turn trains the reader to skip the
channel including the turn it matters.

Not installed. Wiring a user-global hook is the owner's call and comes last.
@wshallwshall
wshallwshall enabled auto-merge (squash) August 14, 2026 20:39
@wshallwshall
wshallwshall merged commit e395f89 into main Aug 14, 2026
44 of 46 checks passed
@wshallwshall
wshallwshall deleted the claude/session-tracking-handoffs-9740d2 branch August 14, 2026 23:41
wshallwshall added a commit that referenced this pull request Aug 15, 2026
… backfill (BACKLOG #1010)

The gate this branch adds went RED on this branch's own update-branch merge, and it was right to.
scripts/coord/fleet.ps1, scripts/coord/seat.ps1 and scripts/hooks/seat-record.ps1 arrived with #399 at
23:41Z tonight carrying no SPDX header: 3 violations across 1273 files checked, with ruff check and
ruff format clean in the same run.

NEITHER PR COULD HAVE CAUGHT THIS ALONE, which is the finding rather than an excuse. #399's author had
no gate to satisfy, because this branch had not landed. This branch's backfill could not cover files
that did not exist when it was written. The incompatibility exists only once BOTH are on main -- which
is what the update-branch produced, and why it surfaced on a PR head instead of on main with the cause
three commits back.

THE WINDOW BETWEEN A BACKFILL AND ITS GATE IS WHERE VIOLATIONS ENTER. A standing note in this project
already says a NEW .py needs the header because an earlier sweep covered only EXISTING files; this
shows the same hole for .ps1 and shows it is not language-specific. ANY file added between a backfill
and its gate landing evades both. The remedy is that the two land close together, and the gap between
them is the exposure.

Verified in a worktree on this branch: the gate exits 0 over 1273 files, measured UNPIPED because $?
after a pipe reports the pager's status and not the gate's -- the trap CLAUDE.md section 11 names by
example, which I walked into on the first attempt. Negative control run in BOTH directions: stripping
the header back off one file returns the gate to exit 1, restoring it returns 0, so the green means
the headers are present rather than that the gate stopped looking.

The claim on #1010 was MOVED from worktree b3-quickwins to this one rather than the citation being
dropped from the subject. The commit-msg gate keys ownership on the COMMITTING worktree, so it refused
the first attempt correctly; dropping the BACKLOG #1010 citation would have cleared the check by making
it look at nothing, which is the dishonest spelling BUILDER.md forbids by name.
wshallwshall added a commit that referenced this pull request Aug 15, 2026
…fill (BACKLOG #1010) (#398)

* feat(quality): licence-header gate, with its negative control and the backfill (BACKLOG #1010)

AGPL-3.0-or-later is asserted twice at project level, in LICENSE and in pyproject.toml, and
per-file provenance was then left to habit. Habit held above 93 percent and decayed silently
across a whole package: messagefoundry/tray/ landed (ADR 0113) with no header on any of its
seventeen files, every one of them wheel content, and nothing reported it.

THE GATE ASSERTS THE VALUE, NOT THE PRESENCE OF THE STRING. A presence-only check passes a file
that affirmatively declares the WRONG licence, and five test modules declared Apache-2.0 in an
AGPL project. Those are corrected here and are reported as their own violation class, never
folded into "missing": an affirmative misstatement is a worse defect than an omission.

SCOPE IS POSITIVE AND WAS MEASURED, NOT ASSUMED. Every tracked file carrying one of six
extensions is in scope, with no tree exemptions. The trees a reader would expect to need one do
not: tee/ (vendored), harness/, samples/, packaging/, docker/, messagefoundry_webconsole/ and
the archived docs/benchmarks/results/ are already fully compliant, and a scan for generated
content finds none tracked. An exemption list would enumerate things that do not exist, which is
worse than no list because it reads as policy and quietly widens. tests/ is in scope on the same
evidence: it sits near 94 percent on its own, which is not what a deliberately-exempt tree looks
like, and nothing anywhere excludes it.

THE GATE SHIPS WITH ITS NEGATIVE CONTROL rather than acquiring one later (BACKLOG #1000). Four
planted classes were each observed failing before it was wired anywhere: a missing header; a
wrong identifier, including the near-miss AGPL-3.0-only that a prefix comparison would accept; a
correct tag inside a STRING LITERAL, which is real here because corepoint_import.py generates
headers and a substring check reads a header-emitting file as a headered one; and a correct tag
BURIED past the head window, which a whole-file grep accepts. The last two are the ways a header
can be present and still not be a header.

Measured over the tracked tree: 1266 files in scope, 1049 already compliant, 212 given a header,
5 wrong identifiers corrected. The backfill splices at a single offset, after any shebang, so
every other byte is untouched and line endings elsewhere are not normalised.

Not yet wired into pre-commit or CI; that is the next commit. The pytest case that runs the gate
over the real tree is included, so the invariant is enforced by the ordinary test job regardless.

* ci(quality): wire the licence-header gate into pre-commit and CI (BACKLOG #1010)

A local pre-commit hook beside ledger-gate and forbidden-content, plus a CI step beside the ruff
ones, so a local commit and a PR enforce the same rule.

SCOPE IS SINGLE-SOURCED IN THE SCRIPT rather than restated per caller. ruff and bandit each had
their scope written twice, drifted, and now need tests/test_lint_scope_parity.py to hold them
together; that whole class is avoided here because the extension map lives in
COMMENT_PREFIXES and nothing else states it. The hook's files: pattern only narrows what
pre-commit bothers to forward, and CI passes no paths at all.

THE CI STEP IS DELIBERATELY NOT PATHS-FILTERED, which is a departure from the ruff steps directly
above it and is the one judgement call here. The changes.code filter keys on

  alwayscode='\.(py|ps1|sh|ts|js|yml|yaml|toml|lock|cfg|ini)$'

which omits .go. A Go-only PR therefore sets code=false, and an inherited filter would skip this
gate on exactly the PR that introduced the headerless file. No .go source is tracked today, so
this is a latent gap rather than a live one -- but BACKLOG #1011 rules on whether a Go tree stays,
and a gate that silently stops covering a language the moment one is added is the failure this
item exists to remove. The scan reads about 1,300 files in under a second, so filtering it would
buy nothing and cost a blind spot.

THE WIRING WAS WATCHED WORK, not merely configured: the hook was run against a planted headerless
file and observed Failed with the file named and the fix quoted, then against a compliant file and
observed Passed. A hook that is present in the config and never fires renders identically to one
that fires and finds nothing, and only the second is evidence.

* test(ide): pin the extension's licence metadata; #1010 step 6's premise was false

BACKLOG #1010 step 6 says ide/package.json declares "SEE LICENSE IN LICENSE" while no ide/LICENSE
exists, and asks for the file to be added or the manifest field corrected. RE-MEASURED BEFORE
ACTING, THE PREMISE DOES NOT HOLD, and neither proposed fix is right:

  vscode:prepublish runs copyFileSync('../LICENSE','LICENSE') and vsce package runs
  vscode:prepublish, so the published .vsix carries the licence by construction;

  ide/.gitignore line 7 lists LICENSE explicitly, so the file's absence from the tree is a
  RECORDED DECISION, not an oversight.

Committing a tracked ide/LICENSE would therefore fight that ignore rule and put a second copy of a
GENERATED file under version control -- two records of one fact with no drift signal between them.
That is the defect this project keeps paying for, not a fix for one. The item's step 6 is closed as
research: there is no dangling pointer.

WHAT WAS GENUINELY MISSING IS THE CONTROL, and the master test plan already had it right in the
words "marketplace metadata is unverified" (12-vs-code-ide-extension.md, P1). Nothing asserted any
of it. Rename the root LICENSE, edit the one-liner, or add LICENSE to .vscodeignore, and the
extension publishes with no licence -- or dies at publish time, the latest and most expensive
moment to learn it. Four assertions now run in the ordinary pytest job:

  the prepublish copy's SOURCE resolves, and is this project's LICENSE rather than some other file;
  the manifest's SEE-LICENSE-IN name matches the file prepublish actually writes;
  .vscodeignore does not exclude the licence and undo the copy;
  ide/.gitignore still records that ide/LICENSE is generated.

The last one is the assertion that would have saved this investigation: the absence looks exactly
like an oversight, so it pins WHY the file is missing where the next reader will meet it.

MUTATION-PROVED rather than merely green: LICENSE was appended to ide/.vscodeignore, the suite went
to 1 failed, the file was reverted and confirmed byte-identical by hash, and the suite returned to
4 passed. The parsed values were also printed to confirm the assertions are not vacuous -- source
'../LICENSE' resolving to the root LICENSE, destination 'LICENSE' matching the declared name.

* backlog: record #1010's gate landing, and the owner action CI cannot see

THE PAIRED LEDGER COMMIT FOR THIS PR, under the sanctioned interim (c): the
required check 'a PR that implements BACKLOG #N must update BACKLOG.md' reads the
PR's own diff, and ledger authoring is not a builder's lane. Third such pairing
tonight, after #1249/#1026 and #1241.

WORTH NAMING: THE GATE FIRED BECAUSE OF A TOKEN I WROTE. It greps the PR title and
body for 'BACKLOG #N', and this PR's title carries it because the Lander put it
there. The claim is ACCURATE -- the PR does implement the item -- so the honest
satisfaction is to update the ledger, not to reword the title until the gate stops
looking. A gate you can silence by changing how you describe the work is not a gate.

THE AMENDMENT CARRIES THE OWNER ACTION, because the ledger is where it survives:
the backfill changed three INSTALLED hook scripts, so machine copies no longer
match source and three tests go red locally on EVERY worktree. Those are
local-machine tests; CI has no user settings and SKIPS them, so A GREEN CI IS NOT
EVIDENCE THE DRIFT IS ABSENT. Remedy is owner-run from a CURRENT checkout, in
order: land, pull, then install -- a stale source DOWNGRADES the gate.

CLOSURE NOT TAKEN. Step 6's premise was separately found false and the item carries
more than the gate itself. Same line held on every pairing tonight.

Verified: 528 items each declaring exactly one status; #1010 still OPEN; U+26A0
unchanged at 121.

* fix(quality): licence headers on the three files #399 added after the backfill (BACKLOG #1010)

The gate this branch adds went RED on this branch's own update-branch merge, and it was right to.
scripts/coord/fleet.ps1, scripts/coord/seat.ps1 and scripts/hooks/seat-record.ps1 arrived with #399 at
23:41Z tonight carrying no SPDX header: 3 violations across 1273 files checked, with ruff check and
ruff format clean in the same run.

NEITHER PR COULD HAVE CAUGHT THIS ALONE, which is the finding rather than an excuse. #399's author had
no gate to satisfy, because this branch had not landed. This branch's backfill could not cover files
that did not exist when it was written. The incompatibility exists only once BOTH are on main -- which
is what the update-branch produced, and why it surfaced on a PR head instead of on main with the cause
three commits back.

THE WINDOW BETWEEN A BACKFILL AND ITS GATE IS WHERE VIOLATIONS ENTER. A standing note in this project
already says a NEW .py needs the header because an earlier sweep covered only EXISTING files; this
shows the same hole for .ps1 and shows it is not language-specific. ANY file added between a backfill
and its gate landing evades both. The remedy is that the two land close together, and the gap between
them is the exposure.

Verified in a worktree on this branch: the gate exits 0 over 1273 files, measured UNPIPED because $?
after a pipe reports the pager's status and not the gate's -- the trap CLAUDE.md section 11 names by
example, which I walked into on the first attempt. Negative control run in BOTH directions: stripping
the header back off one file returns the gate to exit 1, restoring it returns 0, so the green means
the headers are present rather than that the gate stopped looking.

The claim on #1010 was MOVED from worktree b3-quickwins to this one rather than the citation being
dropped from the subject. The commit-msg gate keys ownership on the COMMITTING worktree, so it refused
the first attempt correctly; dropping the BACKLOG #1010 citation would have cleared the check by making
it look at nothing, which is the dishonest spelling BUILDER.md forbids by name.
wshallwshall added a commit that referenced this pull request Aug 15, 2026
…tection left

Builder 3 released #1010's claim after verifying the landing on the ref with a control,
which was the correct call: PR #398 merged 03:49:37Z, adding commit 8e551f4, the gate
ships on main with its negative control and the backfill.

The consequence is what this amendment records. Until now #1010 was protected by a claim,
which is machine-local and unversioned and therefore protected exactly one checkout. That
claim is now gone on that checkout too, so the item is landed, unclaimed, and still
reading as an unstarted P2 quick win -- and a quick-win label is an active invitation
rather than a neutral one.

Also recorded: a banner protects nobody until it is on main. This text is written on a
dispatcher branch and builders read origin/main, so the write and the protection are two
separate events with a gap between them, during which the item is exactly as dangerous as
before anyone noticed. That is the general property of every ledger correction, not a flaw
in this one, and it is the argument for a standing check over a diligent reader.

Carried forward for whoever writes the closure: the window between a backfill and its gate
landing is where violations enter, and it is not language-specific. #399 added three .ps1
files with no header after the backfill was written and before the gate landed; each pull
request was green in isolation and the incompatibility existed only once both were on
main, so no single-PR check could have caught it. That makes the hole structural rather
than anecdotal, and it will recur in the next language the gate learns.

Verified: backlog_status_check.py passes; #1010 still open with exactly one banner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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