traceable EXIT=0 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. Perched sessions are steered to BACKGROUND long-running work so the perch stays reachable — the spt reachability notice, ported from legacy owl's (sibling claude_skill_owl hook_prompt.rs + hook_check.rs) and renamed to spt semantics (, 'active spt perch'). Two injection sites, both perch-gated (a plain CC session is never told it has a perch): (1) UserPromptSubmit — EVERY perched turn's additionalContext closes with the general notice ('when spawning subagents or running long tasks, use run_in_background: true so you stay reachable for incoming messages'), appended AFTER any skill injection + drained messages (a notice never displaces a delivery); (2) PreToolUse — when the tool about to run is a subagent spawn (tool_name Agent, or its older payload name Task — matched both ways so a CC rename in either direction keeps the nudge alive), the targeted launch-this-in-background nudge rides the same emit, messages first, nudge last, and emits EVEN when the drain is empty (the point is catching the foreground spawn before it happens); and (3) PreToolUse — when the tool is `Bash`, `run_in_background` is not true, and the call is LONG-RUNNING by its own declaration (an explicit `timeout` of 60s or more) or by command shape (cargo, npm/pnpm/yarn, gh, pytest/jest, make, cmake, docker, sleep, `find`, `rg`, recursive `grep`), the same nudge rides the emit with a Bash-specific body — CAPPED AT THREE PER TURN (operator's number), the counter reset by each UserPromptSubmit, so the steering is early enough to shape the turn without becoming wallpaper. and (4) PreToolUse — when the tool is a BLOCKING read of a background task's output (`TaskOutput`, its spaced display spelling `Task Output`, or the `BashOutput` sibling — matched all three ways, the same defence against a CC rename the spawn matcher makes), the same nudge rides the emit with a task-output body: backgrounding a task is HOW a perch stays reachable, and then blocking on that task's result spends exactly the reachability the backgrounding bought, while the poll is also unnecessary (completed background work re-invokes the agent on its own). The task-output arm DRAWS ON THE SAME per-turn budget as the Bash arm — ONE shared cap across every capped nudge kind, because the wallpaper the cap prevents is a property of how much a turn says in total, not of any one kind; a polling loop is the exact shape this steers, so a per-kind budget would have leaked the most noise precisely here. The uncapped spawn nudge does not draw on it. Tools that are neither a spawn, nor long-running Bash, nor a blocking task-output read stay silent with an empty drain. Both ride the existing once-capped emit (spill guard unchanged). ADVISORY ONLY, deliberately: PreToolUse returns `additionalContext` and the tool runs anyway, so a nudge cannot convert the call it fires on — only the next one. Deny-and-steer (PreToolUse denying the call so the agent re-issues it backgrounded) is the only lever that would convert the current call and was RULED OUT by the operator 2026-08-04. This buys coverage, not enforcement. WHY (field, 2026-07-07 operator): every claude-spt agent ran subagents/long tasks in the FOREGROUND, going unreachable for the task's whole duration — the legacy-owl notice was the missing steering pressure. WHY THE BASH ARM (2026-08-04 grill): both legacy-owl legs were already ported and shipped, so the honest finding was not 'the feature is missing' but 'the feature does not cover the common case' — long foreground Bash (cargo, gh, test suites, recursive greps) is the ordinary way a perch goes unreachable and fired NOTHING. Observed in the grill session itself: a foreground `grep` blocked 120s and the harness backgrounded it, with the general notice already in context and no targeted nudge existing for that call. 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. Subagent working perches follow the WORKER-TRUTH wave-1 contract (spt-core v0.27.0, doyle freeze 2026-07-06) and NEVER leak silently. SubagentStart (parent context) calls `api worker-start --session-id --agent-id --agent-type ` — NO worker-id positional (core hard-mints `{parent}-w{N}`), NO token (operator ruling: sid-symmetric auth, registration stores the sid, stop accepts stored OR parent-current sid — /clear rotation covered from both ends). The MINTED id is read from worker-start's BARE STDOUT (machine-readable result; empty/refused = LOUD log, never silent) and persisted in adapter-owned state keyed by the CC agent_id (state/worker/.wid — the SubagentStop payload carries only the CC agent_id, so the mapping is the adapter's to hold; same contract-clean carrier as the F-024 sid proof). SubagentStop looks up the minted id and calls `api worker-stop --session-id ` STRICTLY: a refusal is LOUD (stderr reason surfaced — the pre-0.27.0 silent-swallow leaked 100% of workers, the 2026-07-06 six-worker leak), a missing mapping is LOUD (WORKER_STOP_SKIP breadcrumb), and the state row is cleared on success. Missing agent_id on either hook = LOUD skip, never a silent no-op. Floor: min_spt_core 0.27.0 (older cores clap-reject the id-less worker-start — the doyle-accepted skew; core's registration-time floor enforcement guards the reverse direction). 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. A completed /compact must not leave an spt-hosted endpoint stuck ACTIVE with its parked messages undelivered until a human types. Field-proven (deployah 2026-07-21, hertz RCA): doyle's RELEASE GO was SENT (live listener delivery) 79-88ms before the operator submitted /compact; the stub the translation binary typed did not survive the compact collision, the body stayed committed in adapter msgpark, and SessionStart(source=compact) rebound the boundary but never transitioned ACTIVE->IDLE or re-drove the park — so the GO sat 46m45s until the operator's next UserPromptSubmit drained it (same black-hole class as REQ-HAZARD-STUCK-ACTIVE-NO-IDLE / -STOPFAILURE- / -INTERRUPT-, reached via /compact; the interrupt watcher cannot heal it — no interrupt marker). TWO SEAMS: (1) source-certain — the compact arm of handle_session_start must, after a successful boundary rebind, assert `state idle` and re-drive a non-empty msgpark by self-sending a redrive message through the OWN translation binary (--force-native), whose stub opens the turn whose UserPromptSubmit drains the park (custody unchanged: SessionStart never drains/commits the park itself — only a UPS/PreToolUse drain may stage, only post-emit commit destroys). (2) TRIGGER-GATED (the negative leg): an AUTO compact fires the same SessionStart(source=compact) MID-TURN — the agent is genuinely working, so marking idle or injecting a redrive there is the busy-injection hazard, and the continuing turn's next PreToolUse drains the park anyway (REQ-STUB-PARK-NO-STALL). The hook payload carries no trigger; the transcript's compact_boundary record does (`"subtype":"compact_boundary"`, `compactMetadata.trigger` = manual|auto, ground-truthed against live transcripts 2026-07-21). Read the LAST such record via the payload's transcript_path: trigger=auto -> skip (loud log); trigger=manual OR undeterminable (no path / no record / parse fail) -> heal, because the black-hole direction (46min unreachable) beats the noise direction (one stub turn CC queues behind the running turn, self-corrected at its next busy mark). An empty msgpark redrives NOTHING (no dead prompt: an operator compacting an idle session must not wake the agent). The redrive body must be self-explanatory to the receiving agent.