test result: ok. 568 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s FAIL: traceable-reqs check claude-spt can run a session under a DIFFERENT Anthropic account without logging the user out of the first, by launching Claude Code against an adapter-owned ACCOUNT ROOT selected by an alt profile. Claude Code 2.1.239 holds exactly one account per config root (`/login` REPLACES the credentials there; `claude auth` offers only login/logout/status; the published docs state separate CLAUDE_CONFIG_DIR roots are the only way to hold two), so swapping the root at launch is the sole mechanism and no adapter cleverness can avoid it. ONE ROOT PER ACCOUNT, NOT PER ENDPOINT — this is the load-bearing choice and it was arrived at by rejecting the other: credentials live IN the root, so a per-endpoint root means one login of the same account per endpoint, which is disqualifying for the failover this exists to serve (an account runs out of tokens mid-work, the operator switches and CONTINUES THE SAME WORK). What the per-endpoint layout bought — separation of Claude Code's own session state per endpoint — is not worth a login apiece, because per-endpoint memory and identity are spt's job at the mind layer, and endpoints already share one root today. CONTINUITY IS SHARED BY DIRECTORY JUNCTION into every account root: one node-wide tree of `projects/`, `todos/`, `file-history/`, `shell-snapshots/`, `session-env/`. Without a shared `projects/` an account switch ABANDONS the in-flight session, since spt resume passes `--resume ` and Claude Code reads that transcript out of the active root — so the junctioned tree is not a convenience, it is the requirement. CREDENTIALS AND `.claude.json` ARE REAL FILES IN EACH ROOT AND MUST NEVER BE LINKED: measured 2026-08-21 on this node, `.claude.json` takes a NEW FILE IDENTITY every ~15-25 seconds while a session is live (eight consecutive samples, eight distinct inodes, mtime advancing, size constant at 130532) — it is written tmp-then-rename, so a hard link or symlink at that path stops tracking on the first rewrite and the two stores diverge SILENTLY, a token refresh landing in one while the other rots. That is strictly worse than not sharing, and it is the same silent-divergence class as copying credentials in at launch and writing them back at session end (two endpoints on one account clobber each other's refreshed token), which is rejected for the same reason. Directories use JUNCTIONS specifically because they need no elevation, where Windows file symlinks require Developer Mode — the privilege ccs lacks on locked-down boxes, where it silently degrades to directory copies. ACCOUNT IDENTITY IS `.credentials.json` PLUS `.claude.json`'s `userID`/`oauthAccount.*` (measured: those keys differ between two logged-in roots, and a never-logged-in root carries no `oauthAccount` key at all), so treating credentials alone as the account is wrong. SCOPE OF THE MOVE: every spawned role follows the profile — session, resume, psyche_init, echo_commune — because a failover that leaves the Psyche drawing on the exhausted account is not a failover. TRUST MUST BE SEEDED into the root before launch (trust is per-config-root, REQ-HAZARD/F-027): an unseeded root does not fail, it HANGS on the dialog, which would strand the first failover at the worst possible moment. ACCEPTED LOSSES, both deliberate: `history.jsonl` is a FILE, cannot join the junctioned tree, and stays per-account, so prompt history does not follow a switch; and endpoints on one account share that root's Claude Code state, which is the status quo. AN ENDPOINT KEEPS ITS ID ACROSS PROFILES — the profile selects which account pays, never which endpoint is speaking. USERS ADD THEIR OWN ACCOUNTS WITHOUT AN ADAPTER RELEASE via `spt adapter create-profile` (a node-local overlay that survives re-registration and update); spt has NO runtime profile parameterization and no `{profile}` substitution key, so each profile names its own account literally, and `{id}` is available but deliberately unused here. The deliverable is therefore ONE shipped example profile, a launch-shim flag, a one-shot init, and documentation — not a subsystem, and explicitly not a reimplementation of ccs's instance manager, whose per-launch maintenance is the reason its launches are slow. OUT OF SCOPE: settings-type profiles (non-Anthropic backends), provider swapping, and removing ccs — ccs stays installed and keeps its delegation, dashboard and proxy roles, and the shipped `claude-spt:ccs` profile is untouched. First launch on an alt profile SETS ITSELF UP: a launch whose account root is MISSING runs the one-shot `alt init` itself and proceeds, and the account name defaults to "alt" — the user is never asked to run a binary command. Request BigscreenVR/claude-spt-bs#15 (requester discord:reavo, 2026-08-22): v0.29.0 shipped `claude-spt alt init ` as a user-facing setup step, but a user launching the shipped `claude-spt:alt` profile has no straightforward path to the `claude-spt` binary (it lives in the adapter dir, not on PATH), so asking them to run it is unacceptable — setup must happen automatically on the first launch of an endpoint via the alt profile. THE FAST PATH IS PRESERVED, which is what keeps this compatible with the ADR-0010 rejection of per-launch maintenance (the ccs cost this feature exists to remove): init runs at launch in EXACTLY ONE case, Preflight::MissingRoot — every launch after the first pays only the preflight stat it already paid. A FAILED auto-init fails the launch loudly and names `claude-spt alt init ` as the manual repair (the observation is reported, not a diagnosis); a successful one falls through to the normal credential preflight, so the first launch ends at Claude Code's own login prompt exactly as v0.29.0 documented. The default account name "alt" is a shared constant with the shipped profile's literal `--account alt`, so `alt init` bare and the profile agree by construction. The manifest is UNCHANGED — the shipped profile already bakes `--account alt` into every role's command; what this removes is the human step, not a profile knob. Deterministic gates (shell-syntax + unit tests + traceable-reqs check + manifest-schema + docs-drift), each a binary pass/fail, runnable as one command A commune the AGENT did not author must never arm a boundary. Core's echo host writes communes too - its own summary OF the agent - and stamps them with a provenance first line (`Source: echo-commune`); agent-authored drops carry no such header (observed 26/26 across an endpoint's full funnel history: 20 echo frames all headed `Source: echo-commune`, 6 authored frames all opening on the agent's own body). Both legs consumed these blind: the frame leg filtered on kind == COMMUNE and nothing else, and the file leg reads a drop the echo may have written, so an echo whose summary happened to quote a bare marker armed an UNREQUESTED /clear on a live agent mid-work. FIELD-REPORTED by the operator the same day the frame backstop shipped - repeatedly, against a second endpoint. REQ-WAKE-MARKER-QUOTED was an ACCIDENTAL partial shield and not a fix: it held only while the echo's marker mentions happened to be backticked (4 echo frames on the reporting endpoint: raw 2, masked 0), which is a property of the summarized text, not of the rule. THE DIRECTION OF FAILURE IS INVERTED HERE and that is why the guard is broad rather than narrow: for a wake MARKER a dead boundary fails silently, so the mask fails toward arming; for AUTHORSHIP an arm off a body the agent never wrote interrupts a live session, so any `Source: ` provenance header refuses the arm and logs which source it refused. The header is first-line only - a commune that MENTIONS the echo source in its text is still the agent's own. Every /clear boundary on an spt-hosted endpoint RE-ASSERTS the session display name (the boundary rename): CC drops the `-n " @ (/)"` display name when a clear rotates the session, leaving the operator no rendered TUI anchor. The launch shim exports the computed display name as SPT_SESSION_NAME into the spawned CC env (single computation site — the same string that feeds `-n`, parity by construction); the SessionStart hook, on a `clear` boundary, self-sends {"rename":"v1","name":""} --force-native BEFORE the {"checkpoint_fire":"v1"} signal; the translation binary STASHES the name on the rename delivery (answering the mandatory bare {commit}) and emits the rename keystrokes as the FIRST HALF of the fire's ONE COMBINED post-clear sequence (ctrl+s . 50 . `/rename ` . 50 . enter . 150ms enter->ctrl+s bridge . ctrl+s . 50 . wake . 50 . enter . commit) — REVISED v0.15.1: the original design emitted rename and wake as two back-to-back inject sequences, which RACED at the just-rebuilt post-clear boundary (CC input processing lags the PTY stream; the rename's enter registered as a soft newline, the next sequence's ctrl+s failed to stash the residue, and the wake text submitted INSIDE the /rename argument — flynn 2026-07-06, session titled with the wake, NO wake turn, agent dormant ~9.5h, ~33% observed rate). The single sequence removes the inter-sequence race BY CONSTRUCTION; the stash NEVER touches pending wake. Ordering: rename submits before any armed wake fires (/rename is a local no-turn CC command — empirically confirmed 2026-07-05: inline rest-of-line arg, renames the CURRENT session, no agent turn). Missing SPT_SESSION_NAME (pre-0.14.1 spawn, non-shim spawn) => the hook SKIPS the rename send with a loud RENAME_SKIP:no-name log line, never silently; heals on respawn. /compact RETAINS the name (empirically confirmed 2026-07-05) so the compact boundary sends nothing — the existing no-send invariant stands. A rename envelope with a blank/absent name answers a bare {commit} (REQ-HAZARD-EMPTY-RESPONSE-COMMIT discipline), never a stray `/rename ` submit. The /clear|compact SessionStart boundary rotation actually rotates the perch: the hook resolves the endpoint id via $SPT_ENDPOINT_ID FIRST (whoami-by-NEW-sid is a catch-22 — the new sid is unregistered until this very call succeeds, so it resolves self:null and the old code silently skipped rotation), persists the CURRENT session id to an adapter-owned state file ({adapter_dir}/state/session/.sid) at EVERY SessionStart, and presents the PRIOR session's sid from that file as the boundary auth proof (`--session-id `; the verb refuses `AUTH_REFUSED (need --token or matching --session-id)` without it — the departed session's env is gone, so a state file is the only carrier). doyle ruling F024D-BOUNDARY-RULING 2026-07-02: adapter-owned state is the contract-clean surface (glue-model, ADR-0021 family); NOT CLAUDE_ENV_FILE (per-session, dies with the rotation — the exact failure being fixed). When `ccs` is installed alongside `claude`, the post-update plugin reconcile ALSO runs `ccs plugin update sptc@cplugs` (best-effort): ccs-launched sessions read their own per-account plugin tree (the CLAUDE_CONFIG_DIR relocation, ~/.ccs/instances//.claude), which the claude-side reconcile never touches — without the follow-up a ccs session keeps running the STALE plugin after every `spt adapter update`. Not fired when ccs is absent or when ccs IS the primary CLI (already reconciled). Best-effort by design: `update` (never `install` — a ccs account that never installed the plugin is not this reconcile's call), failure → stderr noise only, NEVER a post-update failure and NEVER the stdout arbiter channel. Operator ask 2026-07-01 (v0.10.3). A fresh machine with NEITHER `claude` nor `ccs` on PATH gets Claude Code AUTO-INSTALLED by post-update via the platform-native installer (verbatim the code.claude.com/docs/en/quickstart recommended channel: PowerShell `irm https://claude.ai/install.ps1 | iex`; unix `curl -fsSL https://claude.ai/install.sh | bash`) — the only surface a fresh machine is guaranteed to run (chicken-egg: no claude → no Claude Code → no plugin → no /sptc:setup; grill ruling 2026-07-16). Loud stderr narration; after install the process re-probes PATH then the known ~/.local/bin install location (PATH registration reaches NEW shells only) and continues via the absolute path; install failure is LOUD + exit nonzero carrying the manual one-liner. A present ccs skips the install (documented drop-in — never mutate the system beyond necessity). Authentication stays the operator's first `claude` run; the verified notice says so. The plugin reconcile is VERIFY-THEN-NOTIFY (the documented [update.post] pattern; the 2026-07-16 fresh-install incident proved exit codes alone lie): after the marketplace/plugin steps, post-update RE-READS installed_plugins.json and asserts sptc@cplugs is present — claude-primary verification failure is LOUD stderr + exit nonzero with an empty stdout (no happy notice can fire); verified success prints the ✔-VERIFIED custom stdout notice (custom supersedes the static [update].message); the static [update].message is reworded failure-aware because core's emit_fallback fires it even after a failed post-step (deliberate, documented core design — NOT a core bug; doyle classification 2026-07-16). ccs-primary trees are per-account and unverifiable from this process — they keep the sentinel/static path rather than a false FAIL. A ONE-TIME per-node migration script (per-platform: sh + ps1) repoints an installed claude-spt adapter's [update].repo from the old public repo to the private home, then runs the normal `spt adapter update claude-spt` — no committed pointer in the public repo (the scripts live on the migration/v0.22.0 branch of the private home, fetched via `gh api` per internally-distributed instructions; ADR-0008). Idempotent (already-migrated nodes short-circuit), backs up the manifest before patching, fails loud on an unexpected [update].repo. Endpoints need no action — the updated adapter rides each endpoint's next start. Every release pushes ONE squashed source-only snapshot commit + the vX.Y.Z tag to the public repo (git remote `mirror`) via PLAIN git only — no gh, no GitHub API (ADR-0008). The snapshot excludes the enumerated release/migration plumbing and every file naming the private home (script-authoritative EXCLUDES list); a leak guard hard-fails the mirror on any case-insensitive content OR filename match for the BigscreenVR org / private repo name / -bs shorthand, so a new leak vector must be consciously enumerated, never silently scrubbed. The public tree is deliberately not buildable or installable. The hook binary resolves its own perch id ONLY from `spt whoami --json` — RECEIVE-BOTH shapes: the flat identity object {"id":…} (spt-core ≥ 0.33.0's BREAKING identity-only whoami — the very API the 2026-07-10 UPS-timeout RCA filed for; id:null / NO_PERCH-nonzero ⇒ no-perch) AND the legacy enriched wrapper .self.id (cores < 0.33.0; self:null ⇒ no-perch); any parse failure ⇒ empty. NEVER from a line of the human view, whose first line is a `SUBNET ` roster header on any subnet-member node (the doyle bug 2026-07-01: SessionStart crowned an agent "SUBNET SPT_DEV" and the brief's don't-run-whoami instruction made the wrong identity self-reinforce through an orchestration round). The shape compat is LOCKSTEP-mandatory: an adapter parsing only the legacy wrapper on a 0.33.0 node resolves EVERY harness-hosted fallback identity to empty — shortform, idle marks, and briefs silently die. The live brief must not teach a workaround for a defect spt-core has fixed, and the manifest floor is what makes that safe. Until spt-core 0.68.0 the echo-commune host wrote the SAME path as the agent's drop (.claude/-commune.md) with no arbitration, so a marker-bearing recharge commune written as a turn's LAST act sat unread while the closing message generated and could be overwritten by an echo landing in that window - the file leg lost the race and the frame leg then faithfully read the echo's body and armed nothing (spt-bs-releases#276, claude-spt 0.38.3 interim guidance). The brief's answer was a TIMING rule: take one more tool call after writing a recharge commune, so a hook reads the marker while the drop is still there. spt-core 0.68.0 resolved it at the source, per the node-local published docs (harness-contract/echo-commune.md): the echo ROUTES its brief straight into the durable tiers and never files, commune_dir is the agent's channel alone, and ingest deleting the drop is the success signal. THE MECHANISM THAT MAKES REMOVAL SAFE IS NOT 'core fixed it' BUT THAT THE FRAME LEG NOW READS THE AGENT'S OWN BYTES: nothing overwrites the drop, so a last-act commune still arms through REQ-COMMUNE-FRAME-BACKSTOP with no timing discipline. THE FLOOR IS THE INVARIANT, NOT BOOKKEEPING: a node on 0.67.x running a brief with the workaround removed loses recharge wakes SILENTLY - the dead-boundary-fails-silently shape - so min_spt_core_version >= 0.68.0 is what makes the brief's claims true, and the guard is a COUPLING assertion (brief lacks the timing rule => floor >= 0.68.0) rather than a wording pin, because a test that only pinned text would stay green if someone later lowered the floor. TWO STATEMENTS THE FIX MADE FALSE ARE CORRECTED, NOT MERELY DELETED, and they are the reason this is not a one-line removal: (1) 'the drop file being gone proves nothing either way - either writer's ingest deletes it' had exactly one writer left, and the docs now call the file disappearing the success signal; (2) 'a payload starting Source: echo-commune means the echo won and your delta never landed' would now raise a FALSE ALARM on every legitimate echo frame and drive needless commune rewrites, because core deliberately KEPT the provenance header. THAT HEADER'S CONSUMER IS UNTOUCHED: REQ-COMMUNE-ECHO-NOT-AUTHORED still refuses to arm off any Source:-headed commune, and must - the echo still publishes COMMUNE frames, and arming off one is still arming off a body the agent did not write. History is left alone (manifest version comments, shipped CHANGELOG sections, docs/plans/ECHO-COMMUNE-DROP-COLLISION.md), which is REQ-RECHARGE-VOCABULARY's precedent: rewriting history makes release notes lie about their own releases. The adapter re-render PASSES THROUGH every envelope attribute it does not itself consume, in attribute position with its wire escaping intact, so an attribute published after our render was written is never lost at our seam. BINDING ANSWERED YES (doyle, 2026-08-25, to the discriminating question put the same day): core's REQ-EVENT-ATTR-PASSTHROUGH binds the ADAPTER RE-RENDER and is written with this surface as its audience — the three by-name cases claude-spt built do NOT discharge it. CLAUSE, routed VERBATIM as committed (core docs/event-attr-passthrough @ e1d1e240, riding v0.63.0's docs publish): 'The attribute set is open - re-render by pass-through, never by allowlist. The sender-authored attributes above are a class, not a list: new ones join the envelope over time (seal is only the newest), and they join with their obligations already in force. So the render rule for any consumer that re-emits deliveries (an adapter pipeline, a digest, a relay surface): CARRY THROUGH EVERY ATTRIBUTE YOU DO NOT YOURSELF CONSUME. Re-emitting from a fixed list of known names silently deletes every attribute added after that list was written - and the deletion is invisible at the site that wrote the list, because nothing there ever names what it dropped. The only closed list in this contract is the receiver-composed STRIP class (trust-warning, mnemonics-json), and it is a strip list, not a render list; everything else rides. KEEP THE VALUE IN ATTRIBUTE POSITION WITH ITS WIRE ESCAPING INTACT. An attribute value is attr-escaped for exactly one context. Unescaping it into a body or frame context hands a hostile sender a forgery seam - a crafted value could close your re-rendered tag and land text at your frame level, indistinguishable from a real delivery. Pass the raw attr-escaped span through; decode only where the final consumer parses attributes.' READ ONTO OUR THREE CASES BY THE FILER: `trust-warning` and `mnemonics-json` are CONSUMED by our pipeline (we render their content into our own blocks) and consuming discharges the obligation for those two; `seal` is surfaced and also discharged. EVERY OTHER attribute, including ones that do not exist yet, must ride through to the agent in attribute position, still attr-escaped, on whatever frame we re-emit (the `` / `` family) — never dropped, never unescaped into body context. OUR OWN v0.30.0 MECHANISM GENERALIZES: the raw attr-escaped carry built for `seal` (claude-spt-bs#20, now the contract's mirror clause) is the correct handling for any unknown attribute, so this is a widening rather than a new invention. MOTIVATION, independent of the wording: `hook::render_frames` keeps `from` + body and drops everything else, so each new attribute has had to be caught BY NAME after a field loss — `mnemonics-json` (v0.26.2), `trust-warning` (v0.29.x), `seal` (v0.30.0). Three losses at ONE seam is a recurrence class, not three bugs, and a by-name fix cannot close it because the next attribute is unnamed by construction. TIMING, stated by the filer: this is a NEW obligation entering the contract at v0.63.0, NOT a retroactive defect in v0.30.0 — nothing to file against what shipped, and the generalization is our release on our schedule. PAGE AMENDED 2026-08-25 (rider @ f7137293, same v0.63.0 publish) BECAUSE OUR QUESTION EXPOSED THAT 'CONSUME' WAS READER-DERIVED — the filer amended the PAGE rather than answering only us, which is the difference between a ruling and a contract. Two bullets added, VERBATIM: '"CONSUME" MEANS THE ATTRIBUTE'S DISTINCTION IS RE-EXPRESSED, NOT MERELY READ. A pipeline consumes an attribute when its handling puts the value's meaning back in front of the agent — content re-rendered into the pipeline's own surface, or a dispatch whose AGENT-VISIBLE OUTCOME differs per value (routing on type consumes it only while distinct types yield distinguishable deliveries). Reading a value and then emitting output that two sender-distinct deliveries would share is peeking, not consuming. The test is the DROP TEST: if omitting the attribute makes two deliveries the sender distinguished indistinguishable at the agent surface, it must ride.' and 'ATTRIBUTE NAMES ARE TOKENS, NOT TEXT. The envelope's name grammar is [a-z0-9_-]+. Names are hostile-reachable — envelopes arrive from peer NODES, not only from this binary — so a pass-through re-emits a name only when it matches that grammar; a tag-position span that does not is framing damage, not an attribute. Refuse or drop it LOUDLY, never re-emit it.' NEITHER of our two candidate definitions of 'consume' was right (not 'the re-emitting site reads it' — that is peeking; not 'the agent still needs it' — untestable). CENSUS RUN 2026-08-25 AND THE VERDICT IS SETTLED: `type` is NOT consumed by this pipeline and MUST RIDE. Method, complete rather than sampled: every non-test attribute read in hook.rs is enumerated — `from` (the re-emitted sender), `seal`, `trust-warning`, `mnemonics-json`, and the update nudge's own lookup, which keys on `from == spt-update` and NOT on `type` (parse_update_notify_version); a second sweep for type-keyed literals (notify / alarm / user-msg / "msg") in non-test code found only doc comments and CC-payload types, which are a different vocabulary. So `render_frames` never reads the envelope's `type` at all, and `b` and `b` render BYTE-IDENTICALLY — the drop test failing exactly as written. The idle/live arm is separately type-agnostic BY DESIGN (REQ-STUB-GENERAL-EVENT: notify, user-msg and alarm stub and park identically to a peer msg), and `type` survives there only because frame_envelope retypes the whole opening tag verbatim — carried, never consumed. Our NAMES flag was taken INTO the contract rather than left as adapter defense-in-depth, so the grammar is now a pinned rule and the test must pin exactly it: a grammar-matching unknown name rides; a tag-position span that does not match is refused LOUDLY and never re-emitted. Every Linux binary in the packed `adapter.spt` carries mode 0755 IN THE ARCHIVE, forced at tar time and asserted before the archive is accepted — a non-executable adapter binary cannot be driven at all on Linux (no hooks, no endpoint hosting), so a packaging slip does not merely fail an update, it BRICKS the adapter on every Linux node that installs it. Two traps make this silent: (1) the archive is built on Windows, where no POSIX exec bit exists, so the staged Linux binaries land 0644 while the `.exe` — the one file Linux cannot use — is the only entry marked executable; (2) `chmod 0755` DOES NOT STICK on the Windows build filesystem (measured 2026-07-31: chmod then tar still recorded `-rw-r--r--`), so the obvious fix is a silent no-op and forcing the mode at tar time is the only reliable route. THAT NO-OP IS BUILD-SIDE ONLY, and the distinction must not be carried forward wrong (lia, 2026-07-31): `chmod +x` on an ALREADY-EXTRACTED binary on a Linux node DOES take — mode reads back `-rwxr-xr-x` and the update re-runs clean — so it remains a valid emergency repair for a node stranded before a fixed release lands. It is a workaround and not a fix only because the next version bump re-extracts 0644 and fails identically, NOT because chmod fails there. The archive's self-validation must therefore assert MODES, not just entry names — name-only validation is exactly what let this ship, and it is the same class as measuring a proxy instead of the claim. The session carrier (state/session/.sid) never advances past a FAILED strict registration. Pre-fix, handle_session_start persisted the current sid on EVERY SessionStart even when the bind/boundary spt_strict call was refused (error only logged) — which (a) poisons the NEXT rotation's auth proof (the perch still records the PRIOR sid, so the next boundary presents a sid the perch never accepted → AUTH_REFUSED → stranded perch, the F-024 class reached via carrier skew) and (b) would let the verified-identity fast path (REQ-UPS-IDENTITY-FASTPATH) trust a session the perch never bound. INVARIANT: carrier(eid, sid) is authoritative only when written immediately after a SUCCESSFUL strict bind/boundary registration this SessionStart; on failure the carrier keeps the prior sid (still the true proof for the retry). The seed leg (no endpoint id) never writes. 2026-07-10 UPS-timeout RCA prerequisite (hertz), converged 2026-07-15. The checkpoint clear+wake must NEVER submit the wake before /clear takes effect. A single inject sequence cannot straddle a /clear (the async clear re-runs SessionStart with network I/O, and every sequence must {commit} within the 5s INJECT_COMMIT_DEADLINE), so a fixed post-/clear delay races and the wake lands in the OLD session (field-observed: the followup hit first). The macro is SPLIT and synchronized on CC's own clear-done signal: (ARM) a {"checkpoint":"v1",…} envelope stashes the wake in the translation binary's in-memory pending_wake and emits CLEAR-ONLY (ctrl+s · 50ms · /clear · 50ms · enter · commit); (FIRE) the SessionStart hook, on a `clear` boundary, UNCONDITIONALLY self-sends {"checkpoint_fire":"v1"} and the binary emits WAKE-ONLY (ctrl+s · 50ms · wake · 50ms · enter · commit) iff a wake is armed, else no-ops. State lives ONLY in translate memory (no marker file; the hook is stateless). BOTH self-sends use --force-native so the signal is delivered through the translation binary's stdin (where the markers parse), never spooled to the active-poll channel (the ENLYZEAM plain-text misdelivery mode). Ordering is guaranteed by construction: the wake can only emit after SessionStart, which only fires after /clear completes. Residual (accepted v1): if the checkpoint's own clear fails to fire the signal, the armed wake fires on the NEXT clear — self-limited by the every-clear-fire model, documented in KNOWN-HAZARDS. Fires ONLY on `clear`, not `compact` (no checkpoint variant leverages /compact). The drop detector examines each drop AT MOST ONCE PER IDENTITY and arms at most once: state/commune-seen/.stamp records the LAST EXAMINED identity (:), an unchanged identity is a no-op that does NOT read the file, a changed or missing stamp triggers read-scan-arm-and-restamp, and drop absence clears the stamp. HAZARD INTRODUCED BY REQ-COMMUNE-DROP-DETECT, and it is a shape change rather than a detail: the old trigger was an EVENT (one Write tool call, observed once by construction), the new one is a STATE (a file that persists until core ingests it, >15s and many tool calls later), and every state-shaped trigger re-fires unless something remembers. Without the stamp the detector arms on EVERY hook for the whole ingest window - a second wake-arm self-send, a rewritten clearing latch, and ultimately two post-clear sequences, which BLEND rather than fail loudly (the v0.14.1 boundary-rename scar: never emit two post-clear sequences). The stamp records EXAMINED rather than ARMED deliberately: a commune with no wake marker is the common case, and recording only arms would re-READ a 20-100KB file on every tool call for 15 seconds while never arming. Both halves are load-bearing and both get a test - an arms-exactly-once test and a stat-not-read test asserting the cost model, since a detector that is correct but re-reads is a regression nobody would notice. EXTENDED 2026-09-06 (v0.38.0) TO A SECOND SOURCE: with REQ-COMMUNE-FRAME-BACKSTOP the same drop can be seen TWICE - once on disk before ingest, once as a COMMUNE frame after - so the frame leg stands down whenever a clearing latch is already armed. One drop, one boundary, whichever leg saw it first.