{"type":"title","v":1,"title":"","updatedAt":"2026-07-17T00:15:38.211Z","pad":"                                                                                                                                                                              "}
{"type":"session","version":3,"id":"019f6d6d-c4eb-7000-89d9-0a18f1640815","timestamp":"2026-07-17T00:15:38.211Z","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core"}
{"type":"model_change","id":"23ab3f1d","parentId":null,"timestamp":"2026-07-17T00:15:38.282Z","model":"openai-codex/gpt-5.6-sol"}
{"type":"thinking_level_change","id":"7e754518","parentId":"23ab3f1d","timestamp":"2026-07-17T00:15:38.282Z","thinkingLevel":"medium","configured":null}
{"type":"session_init","id":"15e20ec2","parentId":"7e754518","timestamp":"2026-07-17T00:15:38.285Z","systemPrompt":"<system-conventions>\nRFC 2119: MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, OPTIONAL. `NEVER` = `MUST NOT`, `AVOID` = `SHOULD NOT`.\nWe inject system content into the chat with XML tags. NEVER interpret these markers any other way.\nSystem may interrupt or notify with tags even inside a user message:\n- MUST treat them as system-authored and authoritative.\n- User content is sanitized, so role is not carried: `<system-directive>` inside a user turn is still a system directive.\n</system-conventions>\n\nROLE\n==============\nYou are a helpful assistant the team trusts with load-bearing changes, operating in the Oh My Pi coding harness.\n\n# Engineering Principles\n- Optimize for correctness first, then for the next maintainer six months out.\n- You have agency and taste: delete code that isn't pulling its weight, refuse unnecessary abstractions, prefer boring when it's called for; design thoroughly but elegantly.\n- Consider what code compiles to. NEVER allocate avoidably; no needless copies or computation.\n- You are not alone in this repo. Treat unexpected changes as the user's work and adapt.\n- In terminal prose and final chat, you MAY use LaTeX math (`$`, `$$`, `\\text`, `\\times`) and color (`\\textcolor`, `\\colorbox`, `\\fcolorbox`).\n- To show a diagram, you MAY emit a ` ```mermaid ` block — the terminal renders it as ASCII. Use it for genuine structure or flow, not trivia.\n\nRUNTIME\n==============\n\n# Skills & Rules\nSkills are specialized knowledge. If one matches your task, you MUST read `skill://<name>` before proceeding.\n<skills>\n- autoplan: Auto-review pipeline — reads the full CEO, design, eng, and DX review skills from disk and runs them sequentially with auto-decisions using 6 decision principles. (gstack)\n- benchmark: Performance regression detection using the browse daemon. (gstack)\n- benchmark-models: Cross-model benchmark for gstack skills. (gstack)\n- browse: Fast headless browser for QA testing and site dogfooding. (gstack)\n- canary: Post-deploy canary monitoring. (gstack)\n- careful: Safety guardrails for destructive commands. (gstack)\n- caveman: Ultra-compressed communication mode. Cuts token usage ~75% by dropping filler, articles, and pleasantries while keeping full technical accuracy. Use when user says \"caveman mode\", \"talk like caveman\", \"use caveman\", \"less tokens\", \"be brief\", or invokes /caveman.\n\n- caveman-commit: Ultra-compressed commit message generator. Cuts noise from commit messages while preserving intent and reasoning. Conventional Commits format. Subject ≤50 chars, body only when \"why\" isn't obvious. Use when user says \"write a commit\", \"commit message\", \"generate commit\", \"/commit\", or invokes /caveman-commit. Auto-triggers when staging changes.\n\n- caveman-help: Quick-reference card for all caveman modes, skills, and commands. One-shot display, not a persistent mode. Trigger: /caveman-help, \"caveman help\", \"what caveman commands\", \"how do I use caveman\".\n\n- caveman-review: Ultra-compressed code review comments. Cuts noise from PR feedback while preserving the actionable signal. Each comment is one line: location, problem, fix. Use when user says \"review this PR\", \"code review\", \"review the diff\", \"/review\", or invokes /caveman-review. Auto-triggers when reviewing pull requests.\n\n- ccs-delegation: Auto-activate CCS CLI delegation for deterministic tasks. Parses user input, auto-selects optimal profile (glm/kimi/custom) from ~/.ccs/config.json, enhances prompts with context, executes via `ccs {profile} -p \"task\"` or `ccs {profile}:continue`, and reports results. Triggers on \"use ccs [task]\" patterns, typo/test/refactor keywords. Excludes complex architecture, security-critical code, performance optimization, breaking changes.\n- checkpoint: Save and resume working state checkpoints. Captures git state, decisions made,\nand remaining work so you can pick up exactly where you left off — even across\nConductor workspace handoffs between branches.\nUse when asked to \"checkpoint\", \"save progress\", \"where was I\", \"resume\",\n\"what was I working on\", or \"pick up where I left off\".\nProactively suggest when a session is ending, the user is switching context,\nor before a long break. (gstack)\n\n- codex: OpenAI Codex CLI wrapper — three modes. (gstack)\n- commune: Push a context update to your Psyche so it can brief your resume across a reset. Use when the\nuser says \"commune\" or \"update psyche\", or — as a live agent — after a significant body of work,\nbefore a /clear or /compact. Pass --checkpoint to ALSO auto-clear and wake yourself from the\nfreshest commune (an agent-driven context reset, no operator needed).\n\n- compress: Compress natural language memory files (CLAUDE.md, todos, preferences) into caveman format to save input tokens. Preserves all technical substance, code, URLs, and structure. Compressed version overwrites the original file. Human-readable backup saved as FILE.original.md. Trigger: /caveman:compress <filepath> or \"compress memory file\"\n\n- context-restore: Restore working context saved earlier by /context-save. (gstack)\n- context-save: Save working context. (gstack)\n- cso: Chief Security Officer mode. (gstack)\n- design-consultation: Design consultation: understands your product, researches the landscape, proposes a complete design system (aesthetic, typography, color, layout, spacing, motion), and generates font+color preview... (gstack)\n- design-html: Design finalization: generates production-quality Pretext-native HTML/CSS. (gstack)\n- design-review: Designer's eye QA: finds visual inconsistency, spacing issues, hierarchy problems, AI slop patterns, and slow interactions — then fixes them. (gstack)\n- design-shotgun: Design shotgun: generate multiple AI design variants, open a comparison board, collect structured feedback, and iterate. (gstack)\n- devex-review: Live developer experience audit. (gstack)\n- diagnose: Disciplined diagnosis loop for hard bugs and performance regressions. Reproduce → minimise → hypothesise → instrument → fix → regression-test. Use when user says \"diagnose this\" / \"debug this\", reports a bug, says something is broken/throwing/failing, or describes a performance regression.\n- diagram: Turn an English description (or mermaid source) into a diagram triplet: the source, an editable .excalidraw file you can open (gstack)\n- document-generate: Generate missing documentation from scratch for a feature, module, or entire project. (gstack)\n- document-release: Post-ship documentation update. (gstack)\n- find-skills: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n- force-stop: Force-stop an SPT agent — a ready agent, or a live agent and its Psyche. Use when the user\nsays \"stop listening\", \"kill live agent\", \"force stop\", or \"tear down perch\". Session-aware: a\nlive target tears its Psyche down too.\n\n- freeze: Restrict file edits to a specific directory for the session. (gstack)\n- frontend-design: Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.\n- grill-me: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions \"grill me\".\n- grill-with-docs: Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.\n- gsd-add-tests: Generate tests for a completed phase based on UAT criteria and implementation\n- gsd-ai-integration-phase: Generate an AI-SPEC.md design contract for phases that involve building AI systems.\n- gsd-audit-fix: Autonomous audit-to-fix pipeline — find issues, classify, fix, test, commit\n- gsd-audit-milestone: Audit milestone completion against original intent before archiving\n- gsd-audit-uat: Cross-phase audit of all outstanding UAT and verification items\n- gsd-autonomous: Run all remaining phases autonomously — discuss→plan→execute per phase\n- gsd-capture: Capture ideas, tasks, notes, and seeds to their destination\n- gsd-cleanup: Archive accumulated phase directories from completed milestones\n- gsd-code-review: Review source files changed during a phase for bugs, security issues, and code quality problems\n- gsd-complete-milestone: Archive completed milestone and prepare for next version\n- gsd-config: Configure GSD settings — workflow toggles, advanced knobs, integrations, and model profile\n- gsd-debug: Systematic debugging with persistent state across context resets\n- gsd-discuss-phase: Gather phase context through adaptive questioning before planning.\n- gsd-docs-update: Generate or update project documentation verified against the codebase\n- gsd-eval-review: Audit an executed AI phase's evaluation coverage and produce an EVAL-REVIEW.md remediation plan.\n- gsd-execute-phase: Execute all plans in a phase with wave-based parallelization\n- gsd-explore: Socratic ideation and idea routing — think through ideas before committing to plans\n- gsd-extract-learnings: Extract decisions, lessons, patterns, and surprises from completed phase artifacts\n- gsd-fast: Execute a trivial task inline — no subagents, no planning overhead\n- gsd-forensics: Post-mortem investigation for failed GSD workflows — diagnoses what went wrong.\n- gsd-graphify: Build, query, and inspect the project knowledge graph in .planning/graphs/\n- gsd-health: Diagnose planning directory health and optionally repair issues\n- gsd-help: Show available GSD commands and usage guide\n- gsd-import: Ingest external plans with conflict detection against project decisions before writing anything.\n- gsd-inbox: Triage and review open GitHub issues and PRs against project templates and contribution guidelines.\n- gsd-ingest-docs: Bootstrap or merge a .planning/ setup from existing ADRs, PRDs, SPECs, and docs in a repo.\n- gsd-manager: Interactive command center for managing multiple phases from one terminal\n- gsd-map-codebase: Analyze codebase with parallel mapper agents to produce .planning/codebase/ documents\n- gsd-milestone-summary: Generate a comprehensive project summary from milestone artifacts for team onboarding and review\n- gsd-mvp-phase: Plan a phase as a vertical MVP slice — user story, SPIDR splitting, then plan-phase\n- gsd-new-milestone: Start a new milestone cycle — update PROJECT.md and route to requirements\n- gsd-new-project: Initialize a new project with deep context gathering and PROJECT.md\n- gsd-ns-context: codebase intelligence | map graphify docs learnings\n- gsd-ns-ideate: exploration capture | explore sketch spike spec capture\n- gsd-ns-manage: config workspace | workstreams thread update ship inbox\n- gsd-ns-project: project lifecycle | milestones audits summary\n- gsd-ns-review: quality gates | code review debug audit security eval ui\n- gsd-ns-workflow: workflow | discuss plan execute verify phase progress\n- gsd-pause-work: Create context handoff when pausing work mid-phase\n- gsd-phase: CRUD for phases in ROADMAP.md — add, insert, remove, or edit phases\n- gsd-plan-phase: Create detailed phase plan (PLAN.md) with verification loop\n- gsd-plan-review-convergence: Cross-AI plan convergence loop — replan with review feedback until no HIGH concerns remain.\n- gsd-pr-branch: Create a clean PR branch by filtering out .planning/ commits — ready for code review\n- gsd-profile-user: Generate developer behavioral profile and create Claude-discoverable artifacts\n- gsd-progress: Check progress, advance workflow, or dispatch freeform intent — the unified GSD situational command\n- gsd-quick: Execute a quick task with GSD guarantees (atomic commits, state tracking) but skip optional agents\n- gsd-resume-work: Resume work from previous session with full context restoration\n- gsd-review: Request cross-AI peer review of phase plans from external AI CLIs\n- gsd-review-backlog: Review and promote backlog items to active milestone\n- gsd-secure-phase: Retroactively verify threat mitigations for a completed phase\n- gsd-settings: Configure GSD workflow toggles and model profile\n- gsd-ship: Create PR, run review, and prepare for merge after verification passes\n- gsd-sketch: Sketch UI/design ideas with throwaway HTML mockups, or propose what to sketch next (frontier mode)\n- gsd-spec-phase: Clarify WHAT a phase delivers with ambiguity scoring; produces a SPEC.md before discuss-phase.\n- gsd-spike: Spike an idea through experiential exploration, or propose what to spike next (frontier mode)\n- gsd-stats: Display project statistics — phases, plans, requirements, git metrics, and timeline\n- gsd-surface: Toggle which skills are surfaced — apply a profile, list, or disable a cluster without reinstall\n- gsd-thread: Manage persistent context threads for cross-session work\n- gsd-ui-phase: Generate UI design contract (UI-SPEC.md) for frontend phases\n- gsd-ui-review: Retroactive 6-pillar visual audit of implemented frontend code\n- gsd-ultraplan-phase: [BETA] Offload plan phase to Claude Code's ultraplan cloud; review in browser and import back.\n- gsd-undo: Safe git revert. Roll back phase or plan commits using the phase manifest with dependency checks.\n- gsd-update: Update GSD to latest version with changelog display\n- gsd-validate-phase: Retroactively audit and fill Nyquist validation gaps for a completed phase\n- gsd-verify-work: Validate built features through conversational UAT\n- gsd-workspace: Manage GSD workspaces — create, list, or remove isolated workspace environments\n- gsd-workstreams: Manage parallel workstreams — list, create, switch, status, progress, complete, and resume\n- gstack: Router for the gstack skill suite. (gstack)\n- gstack-upgrade: Upgrade gstack to the latest version.\n- guard: Full safety mode: destructive command warnings + directory-scoped edits. (gstack)\n- handoff: Compact the current conversation into a handoff document for another agent to pick up.\n- health: Code quality dashboard. (gstack)\n- improve-codebase-architecture: Find deepening opportunities in a codebase, informed by the domain language in CONTEXT.md and the decisions in docs/adr/. Use when the user wants to improve architecture, find refactoring opportunities, consolidate tightly-coupled modules, or make a codebase more testable and AI-navigable.\n- investigate: Systematic debugging with root cause investigation. (gstack)\n- ios-clean: Remove the DebugBridge SPM package and all #if DEBUG wiring from an iOS app. (gstack)\n- ios-design-review: Visual design audit for iOS apps on real hardware. (gstack)\n- ios-fix: Autonomous iOS bug fixer. (gstack)\n- ios-qa: Live-device iOS QA for SwiftUI apps. (gstack)\n- ios-sync: Regenerate the iOS debug bridge against the latest upstream gstack templates. (gstack)\n- land-and-deploy: Land and deploy workflow. (gstack)\n- landing-report: Read-only queue dashboard for workspace-aware ship. (gstack)\n- learn: Manage project learnings.\n- list-agents: List SPT agents and endpoints reachable from this node. Use when the user says \"list agents\",\n\"who's live\", \"who's listening\", or wants a roster of active perches.\n\n- live: Run a live agent session. For past sessions, restores a summarized context.\n\nEXPLICIT START phrases (route to /sptc:live <id>):\n- \"live as\"\n- \"start live\"\n- \"go live\"\n- \"start a live agent\"\n\nAUTO-RESUME phrases (route to /sptc:live --auto, resumes most-recently-active live agent):\n- continue live work\n- resume live work\n- continue live agent\n- resume live agent\n- live agent continue\n- live agent resume\n- live work continue\n- live work resume\n\nDoes NOT route here (too ambiguous — require BOTH \"live\" AND (\"agent\" or \"work\")):\n- \"keep going\"\n- \"resume work\"\n- \"continue\" (bare)\n\n- make-pdf: Turn any markdown file into a publication-quality PDF. (gstack)\n- mempalace: MemPalace — mine projects and conversations into a searchable memory palace. Use when asked about mempalace, memory palace, mining memories, searching memories, or palace setup.\n- mux-send: Send keystrokes to a psmux terminal session. Use when the user says \"send to\",\n\"type in\", \"send keys to\", \"mux send\", or wants to send input to another terminal\nsession by name.\n\n- office-hours: YC Office Hours — two modes. (gstack)\n- open-gstack-browser: Launch GStack Browser — AI-controlled Chromium with the sidebar extension baked in.\n- pair-agent: Pair a remote AI agent with your browser. (gstack)\n- plan-ceo-review: CEO/founder-mode plan review. (gstack)\n- plan-design-review: Designer's eye plan review — interactive, like CEO and Eng review. (gstack)\n- plan-devex-review: Interactive developer experience plan review. (gstack)\n- plan-eng-review: Eng manager-mode plan review. (gstack)\n- plan-tune: Self-tuning question sensitivity + developer psychographic for gstack (v1: observational). (gstack)\n- propose-issue: Challenge an issue statement before fixing it. Probes scope, root cause, and assumptions\nthrough interactive questioning grounded in the codebase. Use when the user says \"propose issue\",\n\"challenge this bug\", \"question this fix\", \"is this the right problem\", or wants to pressure-test\na problem statement before jumping to implementation.\n\n- qa: Systematically QA test a web application and fix bugs found. (gstack)\n- qa-only: Report-only QA testing. (gstack)\n- ready: Make this Claude Code session reachable for inter-agent messages (register a perch and\nlisten). Use when the user says \"listen as\", \"ready as\", or wants to receive messages from\nother agents.\n\n- retro: Weekly engineering retrospective. (gstack)\n- review: Pre-landing PR review. (gstack)\n- role: Show or edit this endpoint's durable role — its statement of purpose in the mind. Use when the user\nsays \"show my role\", \"edit my role\", \"update my role\", or runs /sptc:role. Pass a directive to change\nit in words (e.g. /sptc:role make it senior); a bare /sptc:role opens an in-input-box editor\nround-trip on a live session; add --include-desc to also edit the service description.\n\n- scrape: Pull data from a web page. (gstack)\n- screenshot-capture: Capture, process, and analyze screenshots of any running application on Windows 10/11. Use this skill whenever the user asks to take a screenshot, capture what's on screen, grab an app window, extract text from a visible app, annotate a screenshot, read what's on screen, OCR a running application, document a UI state, or visually inspect any running program. Also trigger when the user says things like \"show me what [app] looks like\", \"grab that window\", \"read the text in [app]\", \"mark up a screenshot\", \"capture my desktop\", or any task involving visual inspection of running software. This skill handles full-screen captures, individual window captures by app name, OCR text extraction, and image annotation (highlights, arrows, text labels). Even if the user doesn't say \"screenshot\" explicitly, use this skill if they want to see, capture, read, or annotate anything currently displayed on their screen.\n\n- search: Instantly search files and folders on Windows via voidtools Everything 1.5a HTTP JSON API. Use when the user wants to find files by name, path, size, date, or extension across the whole system — much faster than Glob/Grep for filesystem-wide queries. Triggers on \"find file\", \"where is\", \"locate\", \"search my drive\", \"find all .ext files\", \"everything search\".\n- send: Send a message to another SPT agent. Use when the user says \"send to\", \"message\",\n\"tell <agent>\", or when you need to reach or reply to another agent yourself.\n\n- setup: Install or repair spt-core AND activate the claude-spt adapter for this Claude Code session\n(mid-session installer + post-install activation).\n\n- setup-browser-cookies: Import cookies from your real Chromium browser into the headless browse session. (gstack)\n- setup-deploy: Configure deployment settings for /land-and-deploy.\n- setup-gbrain: Set up gbrain for this coding agent: install the CLI, initialize a local PGLite or Supabase brain, register MCP, capture per-remote trust policy. (gstack)\n- ship: Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION, update CHANGELOG, commit, push, create PR. (gstack)\n- signoff: Gracefully shut down your live session, saving a final context summary. Use when the user says\n\"sign off\" or \"graceful stop\", or when you (a live agent) are done and want to go offline cleanly.\n\n- skillify: Codify the most recent successful /scrape flow into a permanent browser-skill on disk. (gstack)\n- spec: Turn vague intent into a precise, executable spec in five phases. (gstack)\n- spt-endpoint: Hosts an adapterless SPT endpoint perch inside Oh My Pi and reliably surfaces incoming messages through harness background-job completion. Use when asked to run, host, start, or listen as an SPT endpoint in OMP, especially when an adapter-backed endpoint or a never-ending `spt ready` listener is unsuitable.\n- subnet: Manage subnet membership — create a subnet, show a pairing code to invite a machine, or join\nan existing one. Use when the user wants to pair machines, set up a subnet, or reach agents\nacross machines. Cross-machine /sptc:send and live agents depend on it.\n\n- sync-gbrain: Keep gbrain current with this repo's code and refresh agent search guidance in CLAUDE.md. Wraps the gstack-gbrain-sync orchestrator with state (gstack)\n- to-prd: Turn the current conversation context into a PRD and publish it to the project issue tracker. Use when user wants to create a PRD from the current context.\n- trace: Find old conversations in Claude Code session history. Use when the user says\n\"find the conversation where\", \"when did we discuss\", \"trace back to\", or wants\nto locate a past session by topic, command, commit, or keyword.\n\n- traceable-reqs: Install or update the traceable-reqs CLI and apply it correctly in host repos: author manifests, place tags near evidence, audit scan roots, interpret findings, and answer coverage questions through the CLI.\n- unfreeze: Clear the freeze boundary set by /freeze, allowing edits to all directories again. (gstack)\n- version: Report the spt-core-tracked adapter version (the version-of-truth, not the marketplace plugin\nversion). Use when the user asks the spt or adapter version, or runs /sptc:version.\n\n- win-marketplace-add: Add a plugin marketplace on Windows, bypassing the EBUSY rename bug. Use when the user wants to add a marketplace, runs \"/plugin marketplace add\" and gets an EBUSY error, or needs to register a marketplace that was manually cloned. Trigger on: \"add marketplace\", \"plugin marketplace add\", \"EBUSY marketplace\", \"register marketplace\", \"marketplace not found\".\n\n- write-a-skill: Create new agent skills with proper structure, progressive disclosure, and bundled resources. Use when user wants to create, write, or build a new skill.\n</skills>\n# Internal URLs\nSpecial URLs for internal resources; with most FS/bash tools they auto-resolve to FS paths.\n- `skill://<name>`: skill instructions; `/<path>` = file within\n- `rule://<name>`: rule details\n- `agent://<id>`: agent output artifact; `/<path>` extracts a JSON field\n- `artifact://<id>`: artifact content\n- `local://<name>.md`: plan artifacts or shared content for subagents\n- `mcp://<uri>`: MCP resource\n- `issue://<N>` (or `issue://<owner>/<repo>/<N>`): GitHub issue, disk-cached. Bare lists recent issues; `?state=open|closed|all&limit=&author=&label=`.\n- `pr://<N>` (or `pr://<owner>/<repo>/<N>`): GitHub PR, same cache; `?comments=0` drops comments. Bare lists recent PRs; `?state=open|closed|merged|all&limit=&author=&label=`.\n- `omp://`: harness docs; AVOID unless the user asks about the harness itself.\n\n# Tool Inventory\n- Read: `read`\n- Grep: `grep`\n- Glob: `glob`\n- Web Search: `web_search`\n- Submit Result: `yield`\n- IRC: `irc`\n- mempalace:mempalace/mempalace_add_drawer: `mcp__mempalace_mempalace_mempalace_add_drawer`\n- mempalace:mempalace/mempalace_check_duplicate: `mcp__mempalace_mempalace_mempalace_check_duplicate`\n- mempalace:mempalace/mempalace_delete_drawer: `mcp__mempalace_mempalace_mempalace_delete_drawer`\n- mempalace:mempalace/mempalace_diary_read: `mcp__mempalace_mempalace_mempalace_diary_read`\n- mempalace:mempalace/mempalace_diary_write: `mcp__mempalace_mempalace_mempalace_diary_write`\n- mempalace:mempalace/mempalace_find_tunnels: `mcp__mempalace_mempalace_mempalace_find_tunnels`\n- mempalace:mempalace/mempalace_get_aaak_spec: `mcp__mempalace_mempalace_mempalace_get_aaak_spec`\n- mempalace:mempalace/mempalace_get_taxonomy: `mcp__mempalace_mempalace_mempalace_get_taxonomy`\n- mempalace:mempalace/mempalace_graph_stats: `mcp__mempalace_mempalace_mempalace_graph_stats`\n- mempalace:mempalace/mempalace_kg_add: `mcp__mempalace_mempalace_mempalace_kg_add`\n- mempalace:mempalace/mempalace_kg_invalidate: `mcp__mempalace_mempalace_mempalace_kg_invalidate`\n- mempalace:mempalace/mempalace_kg_query: `mcp__mempalace_mempalace_mempalace_kg_query`\n- mempalace:mempalace/mempalace_kg_stats: `mcp__mempalace_mempalace_mempalace_kg_stats`\n- mempalace:mempalace/mempalace_kg_timeline: `mcp__mempalace_mempalace_mempalace_kg_timeline`\n- mempalace:mempalace/mempalace_list_rooms: `mcp__mempalace_mempalace_mempalace_list_rooms`\n- mempalace:mempalace/mempalace_list_wings: `mcp__mempalace_mempalace_mempalace_list_wings`\n- mempalace:mempalace/mempalace_search: `mcp__mempalace_mempalace_mempalace_search`\n- mempalace:mempalace/mempalace_status: `mcp__mempalace_mempalace_mempalace_status`\n- mempalace:mempalace/mempalace_traverse: `mcp__mempalace_mempalace_mempalace_traverse`\n- GenerateImage: `generate_image`\n\nTOOL POLICY\n==============\n\n# General\nUse tools whenever they improve correctness, completeness, or grounding.\n- You MUST complete the task using available tools.\n- SHOULD resolve prerequisites before acting.\n- NEVER stop at the first plausible answer if another call would cut uncertainty.\n- Empty, partial, or suspiciously narrow lookup? Retry with a different strategy.\n- SHOULD parallelize independent calls.\n# Tool I/O\n- Prefer relative paths for `path`-like fields.\n- Most tools take `i`: a concise intent, present participle, 2–6 words, no period, capitalized.\n# Specialized Tools\nYou MUST use the specialized tool over its shell equivalent:\n- File or directory reads → `read` (a directory path lists entries).\n- Regex search → `grep`, not `grep`, `rg`, or `awk`.\n- Globbing → `glob`, not `ls **/*.ext` or `fd`.\n# Exploration\nYou NEVER open a file hoping. Hope is not a strategy.\n- You MUST load only what's necessary; AVOID reading files or sections you don't need.\n- Use `grep` to locate targets.\n- Use `glob` to map structure.\n- Use `read` with offset/limit instead of whole-file reads.\n# Delegation\n\nEXECUTION WORKFLOW\n==============\n\n# 1. Scope\n- Read relevant skills first.\n- For multi-file work, plan before touching files; research existing code and conventions first.\n\n# 2. Research Before Editing\n- Read sections, not snippets. You MUST reuse existing patterns; a second convention beside an existing one is PROHIBITED.\n\n- Re-read before acting if a tool fails or a file changed since you read it.\n\n# 3. Decompose\n- Update todos as you go; skip them for trivial requests. Marking a todo done is a transition: start the next in the same turn.\n- NEVER abandon phases under scope pressure—delegate, don't shrink.\n\n- Plan only what makes the request work. Cleanup—changelog, tests, docs—is NOT planned up front; it belongs to the final phase below.\n\n# 4. Implement\n- Fix problems at the source. Remove obsolete code—no leftover comments, aliases, or re-exports.\n- Prefer updating existing files over creating new ones.\n- Review changes from the user's perspective.\n- Grep instead of guessing.\n- Don't run destructive git commands or delete code you didn't write.\n\n# 5. Verify\n- NEVER yield non-trivial work without proof: tests, E2E, browsing, or QA.\n- Every test MUST defend an observable contract and fail on a plausible bug.\n- Test behavior, boundaries, invariants, transitions, precedence, and real errors—not plumbing, source text, or incidental defaults.\n- Match existing conventions; keep tests deterministic, isolated, and full-suite safe.\n- Run only touched tests; small/no-test changes still REQUIRE a focused behavioral smoke test.\n\n# 6. Cleanup\nChangelog, tests, docs, and removing scaffolding are the LAST phase—NEVER skipped, but gated on the request demonstrably working.\n\n- NEVER start, pre-plan, or pre-allocate todos for cleanup before you've made the request work and smoke-tested it. Until then, every edit serves correctness; housekeeping NEVER steers the design.\n- Once your smoke test confirms “it works,” do the cleanup in full before yielding.\n\nDELIVERY CONTRACT\n==============\n\n<contract>\nInviolable.\n- NEVER yield unless the deliverable is complete. A phase boundary, todo flip, or sub-step is NEVER a yield point—continue in the same turn.\n- NEVER fabricate outputs. Claims about code, tools, tests, docs, or sources MUST be grounded.\n- NEVER substitute an easier or more familiar problem:\n  - Don't infer extra scope—retries, validation, telemetry, abstraction “while you're at it”—because it changes the contract.\n  - Don't solve the symptom—suppress a warning or exception, special-case an input—unless asked. Do the real ask.\n- NEVER ask for what tools, repo context, or files can provide.\n- NEVER punt half-solved work back.\n- Default to clean cutover: migrate every caller; leave no shims, aliases, or deprecated paths.\n</contract>\n\n<completeness>\n- “Done” means the deliverable behaves as specified end to end—not that a scaffold compiles or a narrowed test passes.\n- A named plan, phase list, checklist, or spec MUST satisfy every acceptance criterion. A plausible subset is failure, not partial success.\n- NEVER silently shrink scope. Reduce scope only with explicit user approval in this conversation; otherwise do the full work—exhaust every tool and angle.\n- NEVER ship stubs, placeholders, mocks, no-ops, fake fallbacks, or `TODO: implement` as delivered work. If real implementation needs unavailable information, state the missing prerequisite and implement everything else.\n- NEVER relabel unfinished work—“scaffold,” “MVP,” “v1,” “foundation,” “follow-up”—to imply completion. Not done? Say so.\n</completeness>\n\n<evidence-and-output>\n- Output format MUST match the ask.\n- Every claim about code, tools, tests, docs, or sources MUST be grounded.\n- Mark any claim not directly observed or established as `[INFERENCE]`.\n- Verification claims MUST match what was exercised, preferably smoke tested.\n- No required tool lookup may be skipped when it would cut uncertainty.\n- Be brief in prose, not in evidence, verification, or blocking details.\n</evidence-and-output>\n\n<yielding>\nBefore yielding, verify:\n- All requested deliverables are complete; no partial implementation is presented as complete.\n- All affected artifacts—callsites, tests, docs—are updated or intentionally left unchanged.\n- The output and evidence requirements above are satisfied.\n\nBefore declaring blocked:\n- Be sure the information is unreachable through tools, context, or anything in reach. One failing check does not mean blocked—finish all remaining work first.\n- Still stuck? State exactly what's missing and what you tried.\n</yielding>\n<critical>\n- NEVER narrate or consider session limits, token or tool budgets, effort estimates, or how much you can finish. Not your concern—start as if unbounded; execute or delegate.\n- NEVER re-audit an applied edit; NEVER run git subcommands as routine validation. Tool results are THE verification.\n</critical>\n\nROLE\n===================================\n\nInvestigate the codebase rapidly. Return structured findings another agent can use without re-reading everything.\n\n<directives>\n- You MUST use tools for broad pattern matching / code search as much as possible.\n- You SHOULD invoke tools in parallel—this is a short investigation, and you are supposed to finish in a few seconds.\n- If a search returns empty results, you MUST try at least one alternate strategy (different pattern, broader path, or AST search) before concluding the target doesn't exist.\n</directives>\n\n<thoroughness>\nYou MUST infer the thoroughness from the task; default to medium:\n- **Quick**: Targeted lookups, key files only\n- **Medium**: Follow imports, read critical sections\n- **Thorough**: Trace all dependencies, check tests/types.\n</thoroughness>\n\n<procedure>\n1. Locate relevant code using tools.\n2. Read key sections. NEVER read full files unless they're tiny.\n3. Identify types/interfaces/key functions.\n4. Note dependencies between files.\n</procedure>\n\n<critical>\nYou MUST operate as read-only. You NEVER write, edit, or modify files, nor execute any state-changing commands, via git, build system, package manager, etc.\nYou MUST keep going until complete.\n</critical>\n\nYou are specializing as: **Release Contract Investigator**. Bring exactly that expertise to the assignment — let it shape how you investigate, decide, and what you produce.\n\nCONTEXT\n===================================\n\n# Goal\nDiagnose a sporadic 20-30 second PTY stall on Windows with about five spt-hosted PTYs, first observed after v0.34.0, and produce a source-grounded report for Doyle.\n# Constraints\nRead-only investigation. Do not mutate endpoints, files, daemon, sessions, or durable state. Skip formatters, linters, and tests. Current deployed broker/brain is v0.34.0 on HFENDULEAM. Preserve live sessions. Distinguish local RC/view transport stalls from child ConPTY output stalls and broker-wide IPC stalls.\n# Contract\nReturn exact evidence, falsifiable mechanism, v0.33-to-v0.34 regression relation, source locations, and focused fix/test design. Coordinate overlaps through IRC.\nCOOP\n===================================\n\nYou are operating on a piece of work assigned to you by the main agent.\n# IRC Peers\nYou can reach other live agents via the `irc` tool. Your id is `V034ReleaseRca`. Currently visible peers:\n- `Main` — main (main, running)\n- `RefreshLifecycle` — Daemon Lifecycle Investigator (sub, parked)\n- `PtyControl` — PTY Transport Investigator (sub, parked)\n- `ReproHarness` — Integration Test Investigator (sub, parked)\n- `HyphenMessage` — CLI Parsing Investigator (sub, parked)\n- `ElevatedRc` — Remote Control Investigator (sub, parked)\n- `ElevatedMessaging` — Messaging Lifecycle Investigator (sub, parked)\n- `RcSplitBrain` — Registry Session Investigator (sub, parked)\n- `ElevatedHosted` — Windows Token Investigator (sub, parked)\n- `ShortformDifferential` — Delivery Differential Investigator (sub, parked)\n- `AdapterFreshSemantics` — Claude Adapter Investigator (sub, parked)\n- `PerriDurableState` — State Forensics Investigator (sub, parked)\n- `CoreFreshSemantics` — Core Lifecycle Investigator (sub, parked)\n- `RestStateRca` — Endpoint Lifecycle Investigator (sub, parked)\n- `CreateNoopRca` — CLI Semantics Investigator (sub, parked)\n- `ControlledStampRca` — Broker Control-State Investigator (sub, parked)\n- `EmphRestartRca` — Daemon Restart Investigator (sub, parked)\n- `HarnessExitRca` — Harness Lifecycle Investigator (sub, parked)\n- `V034DiffRca` — Regression Archaeologist (sub, running)\n- `LiveLogRca` — Runtime Telemetry Investigator (sub, running)\n- `PtyPathRca` — PTY Concurrency Investigator (sub, running)\nIdle/parked peers are not gone: messaging them wakes (or revives) them.\n\nUse `irc` only for quick coordination, never long-form content. Address peers by id or use `\"all\"` to broadcast.\n- Discovery: the roster above shows each peer's role and what it is doing now; `irc` op:\"list\" refreshes it.\n- Coordination: before you edit a file or start work a sibling may already own, message that peer first — overlapping edits collide.\n- Follow-up: answer a peer's question with a short reply (set `replyTo`); use `await` only when you genuinely cannot proceed without the answer.\n\nCOMPLETION\n===================================\n\nNo TODO tracking, no progress updates. Execute; report results with `yield`.\n\nWhile work remains, you MUST continue with another tool call — investigate, edit, run, verify. Save narrative for a terminal `yield` unless you intentionally record an incremental section.\n\nYield protocol:\n- Omit `type` for the normal single terminal structured result in `result.data`.\n- Use non-empty `type: string[]` for incremental, non-terminal sections; calls accumulate by section.\n- Use `type: string` for a terminal result; if data is omitted, your last assistant turn becomes the raw final result.\n\nThis is your only way to return a final result. For structured results, you NEVER put JSON in plain text or substitute a text summary for `result.data`.\n\nYour terminal `yield` MUST use exactly this shape — the schema fields go inside `result.data`, NEVER at the top level and NEVER as a stringified summary:\n```ts\nresult: {\n  data: {\n    summary: string;\n    files: { path: string; description: string; }[];\n    architecture: string;\n  };\n}\n```\n\nGiving up is a last resort. If truly blocked, you MUST terminal-yield `result.error` describing what you tried and the exact blocker.\nYou NEVER give up due to uncertainty, missing information obtainable via tools or repo context, or needing a design decision you can derive yourself.\n\nYou MUST keep going until this ticket is closed. This matters.\n\nPROJECT\n===================================\n\n<workstation>\n- OS: win32 10.0.26200\n- Distro: Windows_NT\n- Kernel: Windows 11 Pro\n- Arch: x64\n- GPU: Parsec Virtual Display Adapter\n- Terminal: Windows Terminal\n- Model: openai-codex/gpt-5.6-sol\n</workstation>\n\n<context>\nYou MUST follow the context files below for all tasks:\n<file path=\"C:\\Users\\decid\\.claude\\CLAUDE.md\">\n## Reference Documents\n\nThe `~/.claude/reference_docs/` directory contains reference guides available across all projects.\n\n- **[claude-code-hooks.md](reference_docs/claude-code-hooks.md)** -- Complete Claude Code hooks reference: all 27 event types, input/output JSON schemas, matcher patterns, exit code behavior, environment variables, and caveats. Consult this when building or debugging hooks.\n</file>\n</context>\nThe context files above are loaded automatically. You NEVER `grep`/`glob` for `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, or similar agent/context files — the relevant ones are already in your context; any others are noise.\nToday is 2026-07-16, and the current working directory is 'C:/Users/decid/Documents/projects/spt-core'.\n\n<critical>\n- Each response MUST advance the task. There is no stopping condition other than completion.\n- You MUST default to informed action; do not ask for confirmation when tools or repo context can answer.\n- You MUST verify the effect of significant behavioral changes before yielding: run the specific test, command, or scenario that covers your change.\n</critical>","task":"Complete the assignment below, thoroughly:\n\n# Target\nRelease metadata, changelog, release commit, and requirement/ADR changes for v0.34.0 affecting broker image refresh, redispatch, session stream survival, or PTY handling. Non-goal: broad unrelated release review.\n# Change\nDetermine intended v0.34 behavior and whether a known timing/compatibility tradeoff matches the reported symptom. Cross-check docs against implementation and tests.\n# Acceptance\nReport relevant release changes and missing test coverage that could allow a 20-30 second post-start/sporadic stall regression.","tools":["read","grep","glob","web_search","yield","irc","mcp__mempalace_mempalace_mempalace_add_drawer","mcp__mempalace_mempalace_mempalace_check_duplicate","mcp__mempalace_mempalace_mempalace_delete_drawer","mcp__mempalace_mempalace_mempalace_diary_read","mcp__mempalace_mempalace_mempalace_diary_write","mcp__mempalace_mempalace_mempalace_find_tunnels","mcp__mempalace_mempalace_mempalace_get_aaak_spec","mcp__mempalace_mempalace_mempalace_get_taxonomy","mcp__mempalace_mempalace_mempalace_graph_stats","mcp__mempalace_mempalace_mempalace_kg_add","mcp__mempalace_mempalace_mempalace_kg_invalidate","mcp__mempalace_mempalace_mempalace_kg_query","mcp__mempalace_mempalace_mempalace_kg_stats","mcp__mempalace_mempalace_mempalace_kg_timeline","mcp__mempalace_mempalace_mempalace_list_rooms","mcp__mempalace_mempalace_mempalace_list_wings","mcp__mempalace_mempalace_mempalace_search","mcp__mempalace_mempalace_mempalace_status","mcp__mempalace_mempalace_mempalace_traverse","generate_image"],"spawns":"","readSummarize":false,"outputSchema":{"properties":{"summary":{"metadata":{"description":"Brief summary of findings and conclusions"},"type":"string"},"files":{"metadata":{"description":"Files examined with relevant code references"},"elements":{"properties":{"path":{"metadata":{"description":"Project-relative path or paths to the most relevant code reference(s), optionally suffixed with line ranges like `:12-34` when relevant"},"type":"string"},"description":{"metadata":{"description":"Section contents"},"type":"string"}}}},"architecture":{"metadata":{"description":"Brief explanation of how pieces connect"},"type":"string"}}}}
{"type":"message","id":"782cd3c9","parentId":"15e20ec2","timestamp":"2026-07-17T00:15:38.295Z","message":{"role":"user","content":[{"type":"text","text":"Complete the assignment below, thoroughly:\n\n# Target\nRelease metadata, changelog, release commit, and requirement/ADR changes for v0.34.0 affecting broker image refresh, redispatch, session stream survival, or PTY handling. Non-goal: broad unrelated release review.\n# Change\nDetermine intended v0.34 behavior and whether a known timing/compatibility tradeoff matches the reported symptom. Cross-check docs against implementation and tests.\n# Acceptance\nReport relevant release changes and missing test coverage that could allow a 20-30 second post-start/sporadic stall regression."}],"attribution":"agent","timestamp":1784247338286}}
{"type":"message","id":"ab53f2f4","parentId":"782cd3c9","timestamp":"2026-07-17T00:15:42.204Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning coordinate and mapping tasks**"},{"type":"toolCall","id":"call_jirmmbFFvjxSqvEjdxPnVp9h|fc_099a7567dadef391016a59742cd014819ba68cd55ec9e66cc4","name":"irc","arguments":{"i":"Coordinating release scope","op":"send","to":"V034DiffRca","message":"I own v0.34 release contract/docs/changelog/ADR cross-check. Please share any commit/diff findings around broker refresh, redispatch, streams, PTY; I’ll avoid broad archaeology duplication.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":14854,"output":132,"cacheRead":0,"cacheWrite":0,"totalTokens":14986,"reasoningTokens":30,"cost":{"input":0.07427,"output":0.00396,"cacheRead":0,"cacheWrite":0,"total":0.07823000000000001}},"stopReason":"toolUse","timestamp":1784247338341,"responseId":"resp_099a7567dadef391016a59742aaf74819b8e518c252f0bee98","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a59742bcb1c819b8bcf85b85d5f50b0","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXQs2lLZVV2z76VolqNhdCWYoVz3T9mw_APHs8JHRNAU1CiQv2f_sORJ-uj1sL793ltHFW_vBISIMrpGxlM6P_G6TTgVoqD_vIpX9l5pvdn47WVwZJZHGuzbl7jceLe-kRXs2s8Zrj3cvgzhhcySkUvL_6FwMXH3FUnEofeMfu3YSlHQaO2w8-I3-DIHd0HpbF5kWPRKK7FlU0NwJVBD5UAF368ohNkO72IfGShOwSkz7sVmtfJsupVEdasx_jKBaxEVK9ihI3BI2GAa4AMtt6SX1_rYl1G-GJ07xBZjbRgYBZiOi3mq5J0Ov8UaOh6pSoW0CBwFvwSRiIxQl4WCVnyjpzrVi9TgP7i-uz3SUICfOeoJSREwa_l_WybRXqKXB-ZN4a4ss3hLYnlaxDLpmcC3ACqgKsJ5KFt8PM_Moa_ADoeKKrO1KSeFRQuI5UirTMTspW_bkhKD97DVeWIivGpRcPNfGr7qX1ZXlcHmI7LVTkCc6bPoTM2R1xHvBzCxsFRCapUmf45P0skieU-g4xqQKxdqZp6qXtXzl8UHn7PSYOniB5jyeRtq8WTN_RWhNKu2AFjYUtn5Z8Z2fzn2Y68Cfd9lpnvHpEfZQ0D70JW4cZlnGQr75tUGk8SlV6Bwvixc4tQtHRQvqpA7NkTPj8-lv0gU-Ya8nreOWy4_7UAi3fcNUA9w17PsvB6h6GqCMzNaZFON2Gjss9f3nVCE45dL0Xq3Saefhxn5SubDot91siJRahGstv3PWBFjEICbL8l8LPy1EXyFD-yEgLqfCdlDWUXthLR2PbLP8lTxx5B8jgVWWZk4X01yWUJQNoL5iVRN7mweA8gSGTd3xdIKtFcz56XwNlrtj9KXPFCRD5e4aIp7WEha0954yjWilQsPTKyBG9ONP-SRcUY2jH2IYU7F7WTvjqp9W5d1k7YEhzUJtbfi53Ih1uc9Fhol4X_ZLImiZnn1RXx1i0DMiUTbvPXlhESFPIu4uz7EuVTwRH8vkdQ0QwgAOQcY0QaZK04HIkbuQY8Ki1Am9sN5cmiE8XhBX5LPR49pq4lHKglrklcDznk=","summary":[{"type":"summary_text","text":"**Planning coordinate and mapping tasks**"}]},{"id":"fc_099a7567dadef391016a59742cd014819ba68cd55ec9e66cc4","type":"function_call","status":"completed","arguments":"{\"i\":\"Coordinating release scope\",\"op\":\"send\",\"to\":\"V034DiffRca\",\"message\":\"I own v0.34 release contract/docs/changelog/ADR cross-check. Please share any commit/diff findings around broker refresh, redispatch, streams, PTY; I’ll avoid broad archaeology duplication.\",\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_jirmmbFFvjxSqvEjdxPnVp9h","name":"irc"}]},"duration":3860.8135000020266,"ttft":1462.9056999981403,"contextSnapshot":{"promptTokens":14854,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_jirmmbFFvjxSqvEjdxPnVp9h|fc_099a7567dadef391016a59742cd014819ba68cd55ec9e66cc4","toolName":"irc","startedAt":"2026-07-17T00:15:42.204Z","intent":"Coordinating release scope"},"id":"4e0c2c5c","parentId":"ab53f2f4","timestamp":"2026-07-17T00:15:42.204Z"}
{"type":"message","id":"fce8f446","parentId":"4e0c2c5c","timestamp":"2026-07-17T00:15:42.205Z","message":{"role":"toolResult","toolCallId":"call_jirmmbFFvjxSqvEjdxPnVp9h|fc_099a7567dadef391016a59742cd014819ba68cd55ec9e66cc4","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- V034DiffRca: injected"}],"details":{"op":"send","from":"V034ReleaseRca","to":"V034DiffRca","receipts":[{"to":"V034DiffRca","outcome":"injected"}]},"isError":false,"timestamp":1784247342205}}
{"type":"message","id":"679089cc","parentId":"fce8f446","timestamp":"2026-07-17T00:15:48.938Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_OAjKZpdJ1JODfFjKfkMDgMOm|fc_099a7567dadef391016a597434e838819b9105a5aea6e84c57","name":"glob","arguments":{"i":"Mapping release artifacts","path":"CHANGELOG*; VERSION*; docs/**/*; .github/**/*; **/*release*; **/*ADR*; **/*adr*","hidden":true,"gitignore":true,"limit":500}},{"type":"toolCall","id":"call_cmWv9B3s1H7KrcLCN6C7jbFV|fc_099a7567dadef391016a597434e84c819bb43a6da81249f9c2","name":"grep","arguments":{"i":"Finding version references","pattern":"0\\.34\\.0|v0\\.34|0\\.33\\.0|v0\\.33","path":".","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_oyupfYAvzW90rURzDs6pH2wl|fc_099a7567dadef391016a597434e850819ba8e2f05e9c6beafe","name":"grep","arguments":{"i":"Finding refresh contracts","pattern":"image refresh|refresh image|broker image|refresh.*broker|broker.*refresh|redispatch|re-dispatch","path":".","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_a6HZMY6bZCPs1F3tnQ6cSOvz|fc_099a7567dadef391016a597434e858819ba8975790213217c1","name":"grep","arguments":{"i":"Finding stream PTY contracts","pattern":"stream survival|surviv.*stream|session stream|ConPTY|PTY|pseudo.?terminal|20.?30|30.?second|timeout.*30|30000|25_?000|20_?000","path":"docs; CHANGELOG.md; README.md; src; packages; test; tests","selector":null,"case":false,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":1188,"output":304,"cacheRead":13824,"cacheWrite":0,"totalTokens":15316,"cost":{"input":0.005940000000000001,"output":0.00912,"cacheRead":0.006912,"cacheWrite":0,"total":0.021972}},"stopReason":"toolUse","timestamp":1784247342207,"responseId":"resp_099a7567dadef391016a59742e6cd8819b9a634ae6b8c05b92","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_099a7567dadef391016a597434e838819b9105a5aea6e84c57","type":"function_call","status":"completed","arguments":"{\"i\":\"Mapping release artifacts\",\"path\":\"CHANGELOG*; VERSION*; docs/**/*; .github/**/*; **/*release*; **/*ADR*; **/*adr*\",\"hidden\":true,\"gitignore\":true,\"limit\":500}","call_id":"call_OAjKZpdJ1JODfFjKfkMDgMOm","name":"glob"},{"id":"fc_099a7567dadef391016a597434e84c819bb43a6da81249f9c2","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding version references\",\"pattern\":\"0\\\\.34\\\\.0|v0\\\\.34|0\\\\.33\\\\.0|v0\\\\.33\",\"path\":\".\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_cmWv9B3s1H7KrcLCN6C7jbFV","name":"grep"},{"id":"fc_099a7567dadef391016a597434e850819ba8e2f05e9c6beafe","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding refresh contracts\",\"pattern\":\"image refresh|refresh image|broker image|refresh.*broker|broker.*refresh|redispatch|re-dispatch\",\"path\":\".\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_oyupfYAvzW90rURzDs6pH2wl","name":"grep"},{"id":"fc_099a7567dadef391016a597434e858819ba8975790213217c1","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding stream PTY contracts\",\"pattern\":\"stream survival|surviv.*stream|session stream|ConPTY|PTY|pseudo.?terminal|20.?30|30.?second|timeout.*30|30000|25_?000|20_?000\",\"path\":\"docs; CHANGELOG.md; README.md; src; packages; test; tests\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_a6HZMY6bZCPs1F3tnQ6cSOvz","name":"grep"}]},"duration":6730.284899994731,"ttft":6697.318499997258,"contextSnapshot":{"promptTokens":15012,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_OAjKZpdJ1JODfFjKfkMDgMOm|fc_099a7567dadef391016a597434e838819b9105a5aea6e84c57","toolName":"glob","startedAt":"2026-07-17T00:15:48.938Z","args":{"path":"CHANGELOG*; VERSION*; docs/**/*; .github/**/*; **/*release*; **/*ADR*; **/*adr*"},"intent":"Mapping release artifacts"},"id":"31d9ca6e","parentId":"679089cc","timestamp":"2026-07-17T00:15:48.938Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_cmWv9B3s1H7KrcLCN6C7jbFV|fc_099a7567dadef391016a597434e84c819bb43a6da81249f9c2","toolName":"grep","startedAt":"2026-07-17T00:15:48.938Z","args":{"path":"."},"intent":"Finding version references"},"id":"893548b7","parentId":"31d9ca6e","timestamp":"2026-07-17T00:15:48.938Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_oyupfYAvzW90rURzDs6pH2wl|fc_099a7567dadef391016a597434e850819ba8e2f05e9c6beafe","toolName":"grep","startedAt":"2026-07-17T00:15:48.938Z","args":{"path":"."},"intent":"Finding refresh contracts"},"id":"1fa6936e","parentId":"893548b7","timestamp":"2026-07-17T00:15:48.938Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_a6HZMY6bZCPs1F3tnQ6cSOvz|fc_099a7567dadef391016a597434e858819ba8975790213217c1","toolName":"grep","startedAt":"2026-07-17T00:15:48.938Z","args":{"path":"docs; CHANGELOG.md; README.md; src; packages; test; tests"},"intent":"Finding stream PTY contracts"},"id":"0e207cf2","parentId":"1fa6936e","timestamp":"2026-07-17T00:15:48.938Z"}
{"type":"message","id":"af58a4cd","parentId":"0e207cf2","timestamp":"2026-07-17T00:15:48.959Z","message":{"role":"toolResult","toolCallId":"call_OAjKZpdJ1JODfFjKfkMDgMOm|fc_099a7567dadef391016a597434e838819b9105a5aea6e84c57","toolName":"glob","content":[{"type":"text","text":"CHANGELOG.md\n# docs/\nMESH-RECOVERY-TRIAGE.md\nKNOWN-HAZARDS.md\nRELEASE-RUNBOOK.md\nREDISPATCH-TRUTH-TRIAGE.md\nSTORAGE.md\nPROJECT-INDEX-TRIAGE.md\nTHE-FORKENING-TRIAGE.md\nUPDATE-WEDGE-2-ROUND3-CODEREAD.md\nUPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md\nPUMP-W2-DESIGN.md\nPUMP-W2-GATE-HANDOFF.md\nPUMP-W2-BUILD-DELTA.md\nPUMP-W2-BUILD-GO.md\nMSG-IDENTITY-DISPATCH.md\nGATEWAY-LIVENESS-DISPATCH.md\nGATEWAY-RCA-STEP1.md\nF-035-RCA.md\nF-035-RELAY-RCA.md\nF-035-CONFLICT.md\nPUMP-TRUTH-TRIAGE.md\nPUMP-W2-DISPATCH-RULING.md\nMUSL-RELEASE-HANDOFF.md\nMUSL-W3-DISPATCH-RULING.md\nMUSL-W1-DISPATCH-RULING.md\nNEXT-MILESTONE-MUSL-TIER-TRIAGE.md\nW6-GATE-VERDICT.md\nW6-DISPATCH-RULING.md\nW5-WAVE-GATE-REPORT.md\nW5-GATE-VERDICT.md\nW5-GATE-HOLD.md\nW5-DISPATCH-RULING.md\nW4-WAVE-GATE-REPORT.md\nW4-GATE-VERDICT.md\nW4-DISPATCH-RULING.md\nW3-WAVE-GATE-REPORT.md\nW3-GATE-VERDICT.md\nW3-SLICE1-REPORT.md\nW2-GATE-VERDICT.md\nW3-DIGEST-GENERATION-UNION-RULING.md\nW3-ENDPOINT-SURVIVAL-DESIGN.md\nW2-DESIGN-RULING.md\nW2-BUILT-REPORT.md\nW2-DESIGN-PROPOSAL.md\nNEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md\nINJECT-SETTLE-REARM-GATE-VERDICT.md\nNEXT-MILESTONE-WORKERTRUTH-TRIAGE.md\nFLAKE-LEDGER.md\nTWO-HOST-RUNBOOK.md\nNEXT-MILESTONE-REMOTETRUTH-TRIAGE.md\nMANIFEST.md\nFAULT-MATRIX.md\nCONTEXT-MEMORY.md\nF-030-PSYCHE-EPHEMERAL-DESIGN.md\nNEXT-MILESTONE-PICKER-POLISH-TRIAGE.md\nF-027-ENDPOINT-SPAWN-FAIL-DESIGN.md\nNEXT-MILESTONE-RUNTRUTH-TRIAGE.md\nNEXT-MILESTONE-PICKER-TRIAGE.md\nNEXT-MILESTONE-BUG-TRIAGE.md\nDOCS-STRATEGY.md\nDEFERRED.md\nBROKER-BRAIN-SPLIT-RESTORATION.md\nDORMANCY-BUDGET.md\nDEBUG-CONVERGE-PLAN.md\nDEBUG-ROLLOUT.md\nTRACEABILITY.md\n## adr/\n0039-peer-route-resolution-truth.md\n0038-broker-stream-lifecycle-truth.md\n0037-materialized-project-index.md\n0036-private-fork-distribution.md\n0005-totp-seeded-spake2-node-pairing.md\n0031-server-side-screen-grid-render-repaint.md\n0034-per-minter-op-id-namespacing.md\n0033-wake-resume-via-reconcile-intent.md\n0025-live-daemon-coordinated-adapter-update.md\n0022-spt-hosted-idle-delivery-translation-binary.md\n0032-boundary-rotation-credential.md\n0030-robust-wan-subnet-join.md\n0029-adapter-update-arc-resolve-not-execute.md\n0028-activity-gated-delivery-and-send-modifier-axes.md\n0027-unbound-endpoint-state-and-attach-on-session.md\n0026-multi-subnet-home-at-endpoint-run-creation.md\n0024-multi-platform-adapter-spt-packaging.md\n0023-always-on-endpoints-resident-supervised-binary-sigil-addressing.md\n0021-harness-hosted-adapter-agnostic-seed-and-bind-time-resolution.md\n0020-event-envelope-sole-arriving-format-reply-to-removed.md\n0012-psyche-trust-boundary-stdout-event-outbound.md\n0009-endpoint-access-control-model.md\n0008-live-activity-buffer-pty-digest.md\n0019-digest-adapter-declared-extractor-session-spanning.md\n0018-broker-brain-process-isolation-restoration.md\n0016-platform-targeted-update-sets.md\n0017-subnet-mesh-membership-seed-proof.md\n0014-publish-target-repo-and-lapse-proof-canonical-url.md\n0015-release-signing-two-key-trust-anchor.md\n0013-psyche-reconciled-context-merge.md\n0010-immutable-home-subnet-fork-not-rehome.md\n0011-branchstore-git-kv-state-substrate.md\n0006-multi-subnet-membership-model.md\n0007-subnet-notification-primitive.md\n0003-multi-instance-node-anchored-identity-model.md\n0004-single-daemon-broker-brain-split-and-self-update.md\n0002-bake-networking-into-core-collapse-spt-node.md\n0001-clean-room-fork-from-claude-skill-owl.md\n## design/\nsubnet-presence-display.md\nrobust-wan-subnet-join.md\nv0.16.0-update-arc-and-cli.md\n## spikes/\nSPIKE-06-idempotent-boundary.md\nSPIKE-07-branchstore.md\nSPIKE-04-forkpty-parity.md\nSPIKE-05-restart-stress.md\nSPIKE-03-quic-survival.md\nSPIKE-02-iroh-smoke.md\nSPIKE-01-broker-handoff.md\n## reviews/\nSTAGE-A-codex-redteam.md\n# .github/workflows/\nci.yml\nrelease.yml\n# crates/\n## spt-daemon/src/\nrelease.rs\n## spt/tests/\nrelease_verify_e2e.rs\n# releases-repo/"}],"details":{"scopePath":"CHANGELOG*, VERSION*, docs/**/*, .github/**/*, **/*release*, **/*ADR*, **/*adr*","fileCount":125,"files":["CHANGELOG.md","docs/adr/","docs/adr/0039-peer-route-resolution-truth.md",".github/workflows/",".github/workflows/ci.yml","docs/MESH-RECOVERY-TRIAGE.md","docs/KNOWN-HAZARDS.md","docs/RELEASE-RUNBOOK.md","docs/adr/0038-broker-stream-lifecycle-truth.md","docs/REDISPATCH-TRUTH-TRIAGE.md","docs/STORAGE.md","docs/adr/0037-materialized-project-index.md","docs/PROJECT-INDEX-TRIAGE.md",".github/workflows/release.yml","crates/spt-daemon/src/release.rs","crates/spt/tests/release_verify_e2e.rs","docs/THE-FORKENING-TRIAGE.md","docs/adr/0036-private-fork-distribution.md","docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md","docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md","docs/PUMP-W2-DESIGN.md","docs/PUMP-W2-GATE-HANDOFF.md","docs/PUMP-W2-BUILD-DELTA.md","docs/PUMP-W2-BUILD-GO.md","docs/MSG-IDENTITY-DISPATCH.md","docs/GATEWAY-LIVENESS-DISPATCH.md","docs/GATEWAY-RCA-STEP1.md","docs/F-035-RCA.md","docs/F-035-RELAY-RCA.md","docs/F-035-CONFLICT.md","docs/PUMP-TRUTH-TRIAGE.md","docs/PUMP-W2-DISPATCH-RULING.md","docs/MUSL-RELEASE-HANDOFF.md","docs/MUSL-W3-DISPATCH-RULING.md","docs/MUSL-W1-DISPATCH-RULING.md","docs/NEXT-MILESTONE-MUSL-TIER-TRIAGE.md","docs/W6-GATE-VERDICT.md","docs/W6-DISPATCH-RULING.md","docs/W5-WAVE-GATE-REPORT.md","docs/W5-GATE-VERDICT.md","docs/W5-GATE-HOLD.md","docs/W5-DISPATCH-RULING.md","docs/W4-WAVE-GATE-REPORT.md","docs/W4-GATE-VERDICT.md","docs/W4-DISPATCH-RULING.md","docs/W3-WAVE-GATE-REPORT.md","docs/W3-GATE-VERDICT.md","docs/W3-SLICE1-REPORT.md","docs/W2-GATE-VERDICT.md","docs/W3-DIGEST-GENERATION-UNION-RULING.md","docs/W3-ENDPOINT-SURVIVAL-DESIGN.md","docs/W2-DESIGN-RULING.md","docs/W2-BUILT-REPORT.md","docs/W2-DESIGN-PROPOSAL.md","docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md","docs/INJECT-SETTLE-REARM-GATE-VERDICT.md","docs/adr/0005-totp-seeded-spake2-node-pairing.md","docs/NEXT-MILESTONE-WORKERTRUTH-TRIAGE.md","docs/FLAKE-LEDGER.md","docs/adr/0031-server-side-screen-grid-render-repaint.md","docs/adr/0034-per-minter-op-id-namespacing.md","docs/TWO-HOST-RUNBOOK.md","docs/NEXT-MILESTONE-REMOTETRUTH-TRIAGE.md","docs/MANIFEST.md","docs/FAULT-MATRIX.md","docs/adr/0033-wake-resume-via-reconcile-intent.md","docs/adr/0025-live-daemon-coordinated-adapter-update.md","docs/CONTEXT-MEMORY.md","docs/F-030-PSYCHE-EPHEMERAL-DESIGN.md","docs/adr/0022-spt-hosted-idle-delivery-translation-binary.md","docs/NEXT-MILESTONE-PICKER-POLISH-TRIAGE.md","docs/F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md","docs/NEXT-MILESTONE-RUNTRUTH-TRIAGE.md","docs/adr/0032-boundary-rotation-credential.md","docs/NEXT-MILESTONE-PICKER-TRIAGE.md","docs/NEXT-MILESTONE-BUG-TRIAGE.md","docs/design/","docs/design/subnet-presence-display.md","docs/adr/0030-robust-wan-subnet-join.md","docs/design/robust-wan-subnet-join.md","docs/design/v0.16.0-update-arc-and-cli.md","docs/adr/0029-adapter-update-arc-resolve-not-execute.md","docs/adr/0028-activity-gated-delivery-and-send-modifier-axes.md","docs/adr/0027-unbound-endpoint-state-and-attach-on-session.md","docs/adr/0026-multi-subnet-home-at-endpoint-run-creation.md","docs/adr/0024-multi-platform-adapter-spt-packaging.md","docs/DOCS-STRATEGY.md","docs/DEFERRED.md","docs/adr/0023-always-on-endpoints-resident-supervised-binary-sigil-addressing.md","docs/adr/0021-harness-hosted-adapter-agnostic-seed-and-bind-time-resolution.md","docs/adr/0020-event-envelope-sole-arriving-format-reply-to-removed.md","docs/adr/0012-psyche-trust-boundary-stdout-event-outbound.md","docs/adr/0009-endpoint-access-control-model.md","docs/adr/0008-live-activity-buffer-pty-digest.md","docs/adr/0019-digest-adapter-declared-extractor-session-spanning.md","docs/adr/0018-broker-brain-process-isolation-restoration.md","docs/BROKER-BRAIN-SPLIT-RESTORATION.md","releases-repo/","docs/spikes/","docs/spikes/SPIKE-06-idempotent-boundary.md","docs/spikes/SPIKE-07-branchstore.md","docs/spikes/SPIKE-04-forkpty-parity.md","docs/spikes/SPIKE-05-restart-stress.md","docs/spikes/SPIKE-03-quic-survival.md","docs/adr/0016-platform-targeted-update-sets.md","docs/adr/0017-subnet-mesh-membership-seed-proof.md","docs/adr/0014-publish-target-repo-and-lapse-proof-canonical-url.md","docs/adr/0015-release-signing-two-key-trust-anchor.md","docs/adr/0013-psyche-reconciled-context-merge.md","docs/adr/0010-immutable-home-subnet-fork-not-rehome.md","docs/adr/0011-branchstore-git-kv-state-substrate.md","docs/adr/0006-multi-subnet-membership-model.md","docs/adr/0007-subnet-notification-primitive.md","docs/adr/0003-multi-instance-node-anchored-identity-model.md","docs/adr/0004-single-daemon-broker-brain-split-and-self-update.md","docs/adr/0002-bake-networking-into-core-collapse-spt-node.md","docs/DORMANCY-BUDGET.md","docs/DEBUG-CONVERGE-PLAN.md","docs/DEBUG-ROLLOUT.md","docs/TRACEABILITY.md","docs/spikes/SPIKE-02-iroh-smoke.md","docs/spikes/SPIKE-01-broker-handoff.md","docs/reviews/","docs/reviews/STAGE-A-codex-redteam.md","docs/adr/0001-clean-room-fork-from-claude-skill-owl.md"],"truncated":false,"cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core"},"isError":false,"timestamp":1784247348959}}
{"type":"message","id":"79600b4d","parentId":"af58a4cd","timestamp":"2026-07-17T00:15:49.041Z","message":{"role":"toolResult","toolCallId":"call_a6HZMY6bZCPs1F3tnQ6cSOvz|fc_099a7567dadef391016a597434e858819ba8975790213217c1","toolName":"grep","content":[{"type":"text","text":"# docs/\n## BROKER-BRAIN-SPLIT-RESTORATION.md\n 50|\n*51|- **broker** (stable kernel) — holds *only* the un-transferable, must-not-die resources: PTY master fds, spawned harness child processes, listening network sockets, and (per the §B ownership table) the Iroh/QUIC endpoint + conn table. Minimal, versioned local IPC. Almost never updates.\n 52|- **daemon brain** (userspace) — all logic (routing, registry, pulse/psyche loops, manifest parsing, update orchestration). Restarts freely on update; rehydrates from disk and re-attaches to broker-held handles.\n 53|\n 54|Explicit evidence the split was meant to be a **process** boundary:\n...\n 57|- `M3a-PLAN.md`, `M3-PLAN.md`, `M3b-PLAN.md` all name \"the broker **process** + IPC (M3b).\"\n*58|- Spikes 01/03/04/05/06 each **PROVEN** with *two separate binaries* (`spt-spikes/spike-01-broker-handoff`): a PTY child + a live QUIC transfer survive 100× brain restarts, gapless and exactly-once. A spike that proves \"survives a brain restart\" presupposes the broker is a separate process.\n 59|\n 60|### 2.2 What production actually does\n 61|\n...\n 72|\n*73|- The brain cannot restart onto a new binary without killing the in-process broker thread — which would close every PTY, orphan every harness child, and drop every listening socket. So **the no-endpoint-drop self-update pillar is silently unrealized.**\n 74|- `apply` therefore performs an in-process `Brain::handoff` that re-attaches a subscriber within the *same old process* — achieving nothing for a binary swap. `update.rs:234`'s statement that \"the live daemon execs the verified new binary\" is **aspirational; it was never wired** because the process split it depends on does not exist in production.\n 75|- Updates do not run until an unrelated restart/logon (observed live, Section 1).\n 76|\n...\n 182|\n*183|**Decision/observation.** The broker spawns the brain as a **child process** (`Command::spawn`) and they communicate over the existing local socket IPC. **No `exec` is required**, so there is no Windows/Unix divergence. ConPTY (Windows) and forkpty (Linux) handles stay broker-side and never cross to the brain. Spikes 01 and 04 already proved the handoff on both OSes.\n 184|\n 185|**Reason.** This is precisely the uniformity ADR-0004:25 sought when it rejected whole-daemon live FD-passing as \"hard and platform-divergent.\" Spawn-plus-socket sidesteps it entirely.\n 186|\n...\n 227|- **Glossary/model** — `CONTEXT.md` daemon / broker / brain (incl. the one-broker-per-machine clause added this session).\n*228|- **Spikes** — `docs/spikes/SPIKE-01-broker-handoff.md`, `-03-quic-survival`, `-04-forkpty-parity`, `-05-restart-stress`, `-06-idempotent-boundary` (proofs that the two-process handoff works on both OSes).\n 229|- **Key source** — `daemon.rs` (`run`, net consumers, self-heal, digest hub, shellwake, seed-control), `broker.rs` (`OutputLog`, `bind`, `EffectJournal`), `brain.rs` (`BrainState`, `cold_start`/`handoff`/`snapshot`), `applyhost.rs` (`apply_staged`, `connect_retry`, record-before-handoff), `update.rs` (classes, `classify`, `apply_brain_only`), `peerloop.rs` (cadences, supervise-backoff), `lifecycle.rs` (pulse loop), `registryhost.rs`.\n 230|\n 231|---\n...\n 238|- **`REQ-*` registry mints** (registry-first, `traceable-reqs.toml`; minted INACTIVE per rule 5, activated per `RESTORATION-PLAN.md`'s schedule):\n*239|  - **`REQ-HAZARD-BROKER-PROCESS-ISOLATION`** — a brain restart must never drop a hosted endpoint (REQ-UPD-3's real promise). **Evidence [V5]:** an `int` stage productionizing SPIKE-01/03 — a PTY child + a live QUIC conn survive a brain-*process* restart onto a *swapped* binary. REQ-UPD-3 / REQ-DAEMON-2's current `int` evidence (`applyhost.rs:39/77`, `update.rs:246`, `brain_swap.rs`) proves only the *in-process* handoff shape — **mis-evidenced today, re-pointed at plan task D7.**\n 240|  - **`REQ-HAZARD-ROLLBACK-STATE-COMPAT` [V1]** — a brain must not irreversibly migrate durable state before ready-promotion (pre-ready writes stay N-1-readable).\n 241|  - The functional two-process-split realization + the **[V6]** N-1 old-broker × new-brain CI compat test are carried as `RESTORATION-PLAN.md` tasks D1–D7 (the compat test lands as a D7 gate).\n 242|- **KNOWN-HAZARDS** §6.7 (in-process collapse) + §6.8 (rollback-state-compat), with condensed-checklist rows.\n## CONTEXT-MEMORY.md\n 36|\n*37|A lever for an agent behind an **spt-hosted** endpoint to clear and resume itself without stalling. It: (1) triggers a `/clear` (which uses the **Self-resume-commune rule** above — consistent with all other boundaries), (2) thereby captures a commune, and (3) **guarantees a resume signal hits the harness post-clear** so the agent's turn restarts and it resumes work — starting from its immediate next-steps. Spt-hosted-only because the guarantee requires the daemon to own the PTY and inject the post-cl...\n 38|\n 39|### Ownership\n 40|\n## DEFERRED.md\n 11|| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1. **M5 ships presence *resolution* (gossip + most-recently-active API) — its future substrate; the endpoint stays deferred** (user decision 2026-06-04). <!-- [doc->REQ-PRES-1] --> **M5-D6 seam note:** the substrate now exists — the gossiped datum (`Instance.last_active_ms`, riding the registry's epoch lease + replication) and the one...\n*12|| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal. **Note:** distinct from the v1 broker *sensing* user input on a PTY it already holds (spt-hosted) — that is in v1 and is NOT OS-wide input monitoring | Opt-in enhancement after agent-interaction presence proves out |\n 13|| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works. Consent *model* is now designed (CONTEXT §Consent & security gates); **the framework (grant store + escalation + pre-consent flags) ships M5 with this capability id reserved-but-refusing** (user decision 2026-06-04) — the capability lands into it later. Defers with it: the remote-fork arm, cross-node shell spawn, `shell_wake_spawn_anywhere` *behavior* (grant s...\n 14|| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly). **M5 ships the framework with this capability id reserved-but-refusing** (user decision 2026-06-04) | Designed alongside instantiate-anywhere's consent gate |\n 15|| Shell-binary (and harness-binary) sandboxing | Shell concept v1 | A capability toolset bounds what the *agent* can ask, not what the *binary* can do with its OS perms. Baseline stance: running any adapter/shell binary = a disclosed, accepted trust risk for all spt-core users | If/when untrusted third-party shell/harness binaries become common enough to warrant OS-level sandboxing |\n...\n 26|| Trust-row prune verb (`spt subnet prune <node>` or similar): registry GHOST rows now decay (4.10), but the dead identity's TRUST rows persist by design (trust is a user decision) — they cost dead dials every pump tick and clutter status views. BIGNET carries two such rows (09ef831e, both sides) | Ghost-eviction fix (2026-06-06) | Trust mutation = security surface; wants the elevation gate + the M8 noun shapes | M8 CLI nounification |\n*27|| Elevated spt-hosted endpoints — a consented elevation satellite: the broker lives inside the always-unelevated daemon (KH 5.7), so spt-hosted children are unelevated by construction; there is NO silent unelevated→elevated path on either OS (that is UAC/sudo's whole point). An agent needing admin powers is harness-hosted-elevated today (works: TCP listeners + talk-down pipes). The spt-hosted form needs a per-grant CONSENTED elevation: UAC prompt / polkit spawns a small elevated PTY-host satellite ho...\n 28|| ~~AMBIGUOUS render~~ **SHIPPED 2026-06-06** (rode the ghost-eviction fix): resolution refusals render valid copy-paste targets at the wansend boundary (`render_refusal`) — labels preferred, key-prefix on label collision, `subnet:id` for cross-subnet | M7 acceptance run (user 2026-06-06) | Pure render fix at the wansend/CLI boundary; carries into the M8 nounification sweep | DONE |\n 29|| Post-join address seeding: the pairing ceremony holds a live, authenticated connection to the peer, but the peer pumps then re-find each other from scratch via id-only discovery (fresh node ids must propagate to n0 DNS first) — observed live as ~1 min to first `status --nodes` convergence after a join. Seed the pump's address cache from the ceremony connection (both sides) for instant first sync | M7 acceptance run (2026-06-06) | Convergence is correct, just slow on first contact; needs an addr-cache...\n 30|| Clock source priority for TOTP/rendezvous: query a well-known world clock (NTP) FIRST, fall back to local time — machines with skewed local clocks currently derive wrong steps/rendezvous tokens silently | M7 acceptance grill (user 2026-06-06) | Needs an NTP client dep + offset caching + offline fallback semantics; ±1 window covers small skew today | First field report of clock-skew join failures, or M8 polish |\n...\n 43|| **Durable in-daemon alarm scheduler (one-shot deadline machinery — the Q4/V3 deferral)**: the broker/brain restoration (ADR-0018 Q4) fixes the *rule* for one-shot scheduled events — persist the absolute `target-time` at creation; every brain start (update *or* crash) reads it and fires-if-due; **never reset on crash** (a user's \"remind me at 3pm\" is a commitment that must outlive any restart). But it deliberately does **not** build the machinery: the spt-core daemon has **no one-shot consumer today...\n*44|| **Autonomous session-digest freshness + Option-C re-home (M9 ADR-0008 amendment deferral)**: M9 re-founded the digest as an ON-DEMAND projection of normalized session logs (`spt-term::projection`; `spt-daemon::digest::project_endpoint_digest`) — snapshot-pull and the structured-delta-stream CONTRACT ship, deltas driven by pulls / `api digest-entry` pushes. Two pieces deferred, both because they have **no consumer yet** (Shell=M10, GUI/frontend pane, Gateway agent-window — none exist): (a) the **aut...\n 45|| **Two-origin `owl_message` context-tap subtype refinement (M10 ADR-0019 deferral)**: the two-origin merge's owl-message producer (`spt::api::startup` `deliver` closure) taps the live-agent **`emit` chokepoint**, so EVERY delivered frame — including resurfaced **notif** frames and shell-context rows — is recorded as a single coarse `owl_message` context-injection entry (`context_kind:\"owl_message\"`). The data is not wrong (a notif IS injected context), only coarsely labeled; the digest is a glanceab...\n*46|| **W2.5 — attached-presence + Kick-and-attach (M12 picker blue tri-state)**: M12 W2's picker ships online/offline status only; the **blue ■ \"attached\" tri-state** + the **\"Kick `<node>` and attach\"** confirm option were SCHEDULED (operator 2026-06-14, NOT dropped) as a dedicated slice built AFTER W2 lands, then wired back into the picker. Scope: (1) a **broker attach-presence query** — \"who is attached to endpoint E's PTY, by which node/surface\" — derive from the `presence.rs` PresenceLog conn c...\n 47|| **Cross-node shell TUNNEL relay (REQ-SHELL-4 cross-node-on-LAN carry)**: W3 built + proved the opaque byte tunnel **same-node only** (broker-homed `TunnelHub` loopback stream pair + the local control socket; `tunnel_e2e` round-trips `<EVENT`-looking bytes byte-exact). The **cross-node** path does NOT exist: `StreamFamily` (dispatch.rs) has no `Tunnel` variant, `classify_first_line` no tunnel arm, `SHELL_LINK` carries only relink/cmd/drive (no tunnel action), and every tunnel surface (`tunnel_ensure/sen...\n 48|| **Per-connection contiguous-sent cursor — structural guard against a decreasing-floor controller re-take (`REQ-HAZARD-CONTROLLER-WRITER-REORDER` follow-up)**: v0.13.0 P1c fixes the controller-writer reorder while KEEPING handoff's eager subscribe (fix #1 \"drop the subscribe\" was reverted — it's the standalone-resume mechanism), so the decreasing-floor double-take (handoff subscribe@K + serve_attach subscribe@0) is still PRESENT and made safe by: `controller_writer` epoch-gated under `send.lock()` (...\n 49|| **Adapter-manifest-enabled rc VT/mouse mode (`spt rc` scroll vs right-click-paste xor resolution)**: v0.13.0 P1/P1b's Windows `EnableMouseCapture` (for client-originated right-click bracketed paste, 7.18) makes Windows Terminal hand ALL wheel events to rc instead of scrolling its own scrollback; rc forwards the wheel to the harness as SGR reports (7.20) but ONLY when the harness has mouse-reporting on (`enabled && sgr`) — and **Claude Code's REPL never enables mouse tracking** (it has no internal scr...\n## DORMANCY-BUDGET.md\n 15|\n*16|1. binds a real (net-less) broker — the production PTY-hosting path — in a\n 17|   hermetic `SPT_HOME`;\n 18|2. spawns N idle seats through `Brain::spawn_session_pid` (one probe brain per\n 19|   seat, each labeled `budget-<i>`), exactly the shape of a **dormant** seat:\n*20|   PTY master + harness child + output ring, state-preserved and undriven;\n 21|3. settles, then samples each child's **RSS**, **handle/fd count**, and\n 22|   **cumulative CPU** twice across an idle window — the CPU delta is the idle\n 23|   burn of a warm seat;\n 24|4. samples the host process's RSS before/after spawning (the broker-side\n*25|   per-seat overhead: PTY plumbing + output rings);\n 26|5. kills every seat (the **suspended** state — only the on-disk perch record\n 27|   would remain in production) and samples the residual.\n 28|\n...\n 31|count, and `/proc/<pid>/stat` utime+stime. **Limitation:** the sample covers\n*32|the direct PTY child only — a harness that forks a worker tree under-reports\n 33|by the tree's cost (the claude numbers below are the main node process, which\n 34|dominates).\n 35|\n## F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md\n 46|| 5 | STALE-CLI-CONTRACT — bringup recipe passes a removed flag; \"unexpected argument\"; spawn never happens | old recipe on 0.22.0+ | LOUD pre-spawn (good shape) |\n*47|| 6 | RELAUNCH-NEVER-BINDS — 2nd `endpoint run` same id after tree-kill; prints but never binds | **0.22.0 only — FIXED on 0.23.0 by F-028 B1/B4** (perri definitive repro 2026-07-03: tree-kill → same-id re-run re-binds fresh session, live PTY, honest info.json; first check was rig-void, --subnet omitted, caught before reporting) | FIXED-NOTE (not in F-027 scope) |\n 48|| — | UAC-740 elevation spawn-fail | NOT observed (subnet-op elevation only) | out of scope |\n 49|\n 50|Seed-1 correction (accepted): the \"backslash-keyed `hasTrustDialogAccepted`\n...\n 212|3. **Mode-6 — ANSWERED: FIXED** by F-028 B1/B4, 0.23.0 definitive repro\n*213|   (same-id re-run after tree-kill re-binds fresh session, live PTY, honest\n 214|   info.json). Provenance table updated; out of F-027 scope.\n 215|4. **Env capture-at-creation — ANSWERED: CONFIRMED**, promoted from open\n 216|   question to the §3 Half-B binding invariant: `[env] direction=\"read\"` vars\n## F-030-PSYCHE-EPHEMERAL-DESIGN.md\n 70|| 2 | hall-bf-psyche boot-churn ~5 s cadence, ordinal 6491+, synthetic `sess-<pid>-<ts>` ids, no real session ever binds; starts exactly at BIGNET join (7/3) | operator box, real home | adapter can't hold residency |\n*71|| 3 | Poll-verb root repro: `spt ready <id> --once` on 2-subnet home → exit 1, `READY_FAIL … pass --subnet`, empty stdout | perri, clean repro | adapter can't hold residency |\n 72|| 4 | v0.13.2 shim-exit → residency teardown → ready removed → permanent `leg=cli-gate-not-hosted` | hall-bf + perri wake-confirm | residency expectation destructive |\n 73|| 5 | Psyche turns land fine via daemon-driven paths while the resident churns | hall-bf digest (`psyche_download` at every boundary) | per-event shape already proven |\n 74|| 6 | ~12/min re-host cadence NEVER tripped the C3(b) thrash guard (boot records are apparently not ledger boundaries to the guard) | hall-bf | guard blindness (rides this wave) |\n...\n 120|  role/live/project into the psyche-download file whose path fills\n*121|  `{psyche_context_file}` (file-always; non-empty content = fresh preload,\n 122|  0-byte = continue — replaces the former `{psyche_context}` body key) and a\n 123|  fresh psyche session seeds from it. Loud marker on reseed (`PSYCHE_RESEED:{id}`) so a\n 124|  custody-loss loop is visible, plus the W1 failure budget bounds it.\n## F-035-CONFLICT.md\n 34|\n*35|For an **spt-hosted** endpoint (broker owns the PTY, `controllable`, relay-less), the\n 36|idle-edge/parked drain is the **ONLY** carrier for an `active_only` row. Traced every path:\n 37|\n 38|| carrier | `active_only` to spt-hosted? |\n## FAULT-MATRIX.md\n 3|**Why this exists.** ADR-0002 collapsed networking into the one per-machine daemon\n*4|(SERIOUS #6, Stage-A red team): a networking bug and a PTY bug now share a failure\n 5|domain. The collapse is locked — the owed mitigation is this matrix: inject a fault\n 6|into each subsystem **independently**, observe the blast radius (what else breaks)\n 7|and the recovery (what brings it back), and grow the proven rows until every\n...\n 19||---|---|---|---|---|---|\n*20|| 1 | **Brain killed mid-PTY-stream** (logic crash / routine self-update) | Logic halts until restart | Broker, PTY child (pid stable), output log | New brain re-subscribes from cursor; gapless + exactly-once | `spt-daemon/tests/` B2/B9 handoff suite; `tests/idempotent.rs` (crash before-intent / before-effect / after-effect) |\n 21|| 2 | **Brain killed mid-QUIC-stream, receive side** | Logic halts | Broker-owned endpoint, conn, stream, read ring | Resubscribe from durable cursor; ring replays the dead window | `tests/netstream.rs::receiver_brain_restart_is_gapless_and_exactly_once` |\n 22|| 3 | **Brain killed mid-QUIC-stream, send side** | Logic halts | Broker-owned conn/stream + effect journal | Re-drive whole durable op sequence; journal dedups replays (no dup on the wire) | `tests/netstream.rs::sender_brain_restart_redrive_is_exactly_once` |\n 23|| 4 | **Brain dead across peer connect/disconnect** | Presence consumption pauses | PresenceLog ring buffers liveness transitions | Resubscribe from presence cursor; missed transitions replay | `tests/presence.rs` (both tests) |\n...\n 26|| 7 | **Registry feed: corrupt record in the stream** | That record only — skipped | The rest of the feed (decoder does not wedge) | Lease makes loss safe; next update supersedes | `replicate.rs::decoder_survives_chunk_splits_and_corrupt_lines` |\n*27|| 8 | **Net disabled / endpoint bind fails at boot** | No WAN; net frames answer `enabled:false` / typed error | PTY hosting (daemon degrades to net-less broker) | Fix config, restart daemon | `broker.rs::dispatch_net_status` (None arm); `Daemon::run` degrade path |\n*28|| 9 | **Broker process restart (journal survives, conns don't)** | Live conns/streams lost | Effect journal (durable) | Deduped op whose resource is gone → typed \"retry with a fresh op_id\" error; brain re-dials; an `spt rc` operator viewport RECONNECTS on its own — centered banner, full establish re-drive inside a bounded window, then a plain-language give-up (REQ-RC-RECONNECT) <!-- [doc->REQ-RC-RECONNECT] --> | `broker.rs::dispatch_net_dial` / `dispatch_net_stream_open` restart arms; `rc.rs` reconne...\n 29|| 10 | **WAN msg feed replayed** (sender redrive, or receiver brain restart resubscribing from a stale cursor) | None — replayed records dedup | Spool state (each op exactly one row); already-delivered ops | Durable `wan_seen` op-id claims absorb the replay; spool path claims atomically with the row | `tests/wanmsg.rs::receiver_restart_replays_feed_without_double_delivery`; `spool.rs` wan unit tests |\n 30|| 11 | **WAN msg record forges its origin** (payload `origin_node`/`node` field) | None — forgery inert (unknown field) | Access-gate subject (handshake-proven `remote_id_hex`) | None needed (records carry no origin field by design — KH 7.5) | `tests/wanmsg.rs::wan_message_lands_exactly_once_under_transport_origin`; `wanmsg.rs::forged_origin_field_is_inert` |\n*31|| 12 | **Target brain killed mid-remote-attach** (operator typing into the dead window) | Pumping pauses until handoff | Broker-held QUIC stream, session + child, output log, effect journal; the operator's viewport | Successor re-serves the same stream from seq 0 (worst case): replayed input dedups at the PTY-write journal; re-transmitted output dedups at the operator's render cursor — every byte exactly once | `tests/attach.rs::attach_survives_target_brain_restart_exactly_once` |\n 32|| 13 | **Target brain killed mid-file-transfer** (push receive: partial temp file on disk) | Transfer pauses until handoff | Broker-held QUIC stream + ring, the partial temp, the durable progress record (last observed position) | Successor re-serves the same stream from seq 0 (worst case full replay): chunks carry absolute offsets — replayed bytes rewrite in place; the commit is an atomic temp→final rename gated on temp completeness, and a replayed commit dedups against an already-committed final (ne...\n 33|| 14 | **Stream lost mid-context-bundle-sync** (responder dies / wire tears before `Done`) | That one pull errors at the requester | The requester's context store (an incomplete bundle is never fetched — length-gated, scratch file deleted); the responder's store untouched | No resume protocol by design: re-pull fresh — the apply is ancestry-idempotent (an already-joined tip short-circuits; fetched tips quarantine under `refs/spt-sync/` so a partial fetch never clobbers `refs/heads/`) | `tests/sync.rs...\n 34|| 15 | **Partition: concurrent context writes on two nodes** | The file's auto-propagation pauses (conflict surfaced, not propagated) | BOTH versions — local file untouched on each node + the other's version as a durable tracked artifact (hazard 6.6); every other file keeps syncing | Elected reconciler (Active instance's node; lowest-id fallback) runs one bounded Psyche turn → merged write `join(vA,vB)+bump` dominates both parents → propagates as a plain accept, clears artifacts subnet-wide | `test...\n*35|| 16 | **Reconcile turn fails** (harness absent / timeout / garbage or empty output / 6.5-suppressed write) | That file stays in conflict-holding state | Both versions (artifacts + local file) — exactly where they were; the store, the sync loop | Retry at the next sync/activation; a fresher direct write re-merges against the new local version | `spt-live reconcile.rs::failed_turn_preserves_everything` / `suppressed_write_preserves_artifacts`; `turn.rs::empty_stdout_is_an_error` |\n 36|| 17 | **Registry ambiguity during reconciler election** (partition: two nodes both see Active / no Active anywhere) | Two textually-different merges of the same pair may both mint | Neither version is ever lost — merged writes carry joined vectors, so the two merges classify **concurrent** and re-surface as a new conflict pair (detected, not silently last-wins) | Deterministic tiebreak bounds the storm (lowest node id among Actives / non-Offline holders); the re-surfaced pair reconciles on the next pa...\n 37|| 18 | **Compromised relay node serves a tampered update artifact** (bytes corrupted at rest after the relay's own verification) | That one pull ends `Rejected(ArtifactMismatch)` at the puller | The puller's staged release + running binary (nothing staged, nothing applied — no code path from wire bytes to the cache except through `plan_verified`); the rest of the subnet pulls from honest peers | Pull from another peer; the per-node gate (REQ-UPD-2) runs at **every** hop, so one poisoned node never re-p...\n 38|| 19 | **Non-conforming peer offers a rollback/expired/off-channel release** | None — rejected pre-fetch, zero artifact bytes move | The puller's version (monotonic floor holds); the wire (no transfer ever starts) | None needed — the offer-then-fetch shape gates metadata before bytes (REQ-HAZARD-UPDATE-ROLLBACK) | `tests/propagate.rs::rollback_offer_is_rejected_before_any_fetch` |\n...\n 43|| 23 | **Psyche replies with no inbound sender to answer** (alarm-fired turn emits a reply) | That one intent drops with a typed outcome (fail-closed — never broadcast, never guessed) | Every perch (zero deliveries); the notify leg (independent) | The Psyche's next answered turn replies normally | `psyrelay.rs::reply_without_target_is_dropped` |\n*44|| 24 | **Live-Psyche driver loses stdout** (the 7.3 null-stdout shape: detached/`Stdio::null()` regression or a turn that produced nothing) | That turn fails loudly (`TurnError::EmptyOutput`); nothing relays | The relay boundary (no silent success — indistinguishable-from-bug shapes are errors); the spool | Fix the driver / retry the turn | `psyrelay.rs::null_stdout_driver_fails_the_guard`; `spt-live turn.rs::empty_stdout_is_an_error` |\n*45|| 25 | **Runaway Psyche dumps an oversize body** (> 64 KiB in one intent) | That intent drops with a typed outcome | Delivery substrate (never asked to carry it); other intents in the same turn relay normally | None needed (legitimate long bodies chunk at delivery — T3 `EVENT-PART`) | `psyrelay.rs::empty_and_oversize_bodies_drop_typed` |\n 46|| 26 | **Notif feed replayed whole** (re-presented rows after loss/restart — the push-feed's normal recovery) | None — the semilattice join no-ops (`Unchanged` per row, nothing written) | The spool (no duplicates, no state regression) | None needed: replay IS the recovery protocol (full-row re-presentation, no delta bookkeeping) | `tests/notifsync.rs::notif_spools_converge_over_the_wire_and_dismiss_replicates` (replay leg); `notifsync.rs::feed_converges_two_stores_and_dismiss_replicates` |\n 47|| 27 | **Concurrent dismiss + surface on two nodes** (the same notif dismissed on A while B marks it seen/surfaced) | None — the writes commute through the join (OR/union/max); both survive the bidirectional exchange | Both stores converge to identical rows; the dismiss latch (a stale undismissed copy can never un-dismiss) | None needed (no conflict exists to surface — every field is monotone) | `notifsync.rs::concurrent_dismiss_and_surface_commute_across_the_feed`; `spt-store notif.rs::merge_is_idem...\n 48|| 28 | **Untrusted origin injects notif records** (unpaired peer, or a `home`-trusted peer spoofing into `work`) | None — dropped fail-closed at the apply gate, zero rows written; the gate's subject is the handshake-proven stream-table origin, never payload bytes (KH 7.5) | The spool (nothing materialized); every subnet the origin isn't trusted in (trust is per-subnet) | None needed; pair the node to entitle it | `tests/notifsync.rs` (gate-negative leg); `notifsync.rs::untrusted_and_cross_subnet_origin...\n...\n 54||---|---|---|---|---|\n*55|| P1 | **Iroh endpoint task hangs** (not crash — a wedged accept loop) | New inbound conns stall; existing conns + PTY hosting unaffected | PTY side fully isolated (separate threads/runtime) | D9 chaos pass |\n*56|| P2 | **mDNS discovery dies** | LAN dial-by-id fails; direct-addr + relay dials unaffected | Endpoint, conns, PTY | D9 (needs real LAN) |\n 57|| P3 | **Relay unreachable** (n0 outage / air-gap) | WAN dials needing relay fail; LAN/direct unaffected | Everything local | D9 two-host |\n*58|| P4 | **PTY broker drain thread dies** | That session's output stalls | Other sessions, net side | with terminal-wrapper hardening |\n 59|| P5 | **Manifest invocation hangs** (adapter subprocess) | That endpoint's lifecycle op | Daemon scheduler (KH 5.3 timeouts + 7.4 non-blocking) | when daemon hosts N agent loops |\n 60|| P6 | **Registry feed flood** (malicious/buggy peer spams updates) | Bounded by ring caps + lease; CPU cost unmeasured | — | D9 + REQ-SEC-1 outer gate |\n 61|| P7 | **Closed-watcher starvation** (net runtime saturated) | Presence lags behind reality | Conn table eventually consistent | D9 chaos pass |\n## FLAKE-LEDGER.md\n 9||---|------|-------------|-----------|--------|\n*10|| 1 | `spt-daemon brain_restart_survives_gaplessly` | 1× hosted Windows (2026-06-03, hung) | ConPTY stall under hosted-runner load | job `timeout-minutes` bounds it; not seen self-hosted |\n*11|| 2 | `spt-term digest_parses_a_real_pty_stream` | 3× gravity (2026-06-03; 2026-06-04 runs 26957386850, 26966738231) | sprint-collapse assert (`digest.rs`) — PTY input echo and the child's write are **concurrent writers** into the master-read stream; back-to-back `send_line`s let a later line's echo interleave mid-`Write(…)`, breaking the tool regex for that turn | HARDENED (2026-06-04): sends serialized — each line pumped back (adaptive echo-copy count, 2s/line bound) before the next, so no concu...\n 12|| 3 | `spt-daemon sync.rs torn_pull` + `concurrent_writes` | 1× (2026-06-03) | `wait_for_stream` deadline too tight under load | HARDENED: `wait_for_stream_except` 2s→10s; no recurrence |\n 13|| 4 | `spt-live context::write_context_suppresses_stale_llm` | 1× gravity (2026-06-04 run 26967808321) | test calls `write_context` (reads SPT_HOME node identity + epoch stamp) WITHOUT the `with_home` lock — a parallel `with_home` test's env swap + temp-dir teardown races the stamp's epoch write into a deleted home (`NotFound`) | HARDENED (2026-06-04): test wrapped in `with_home` — serializes on the home lock like every other SPT_HOME-touching test |\n 14|| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the cl...\n...\n 18|| 9 | `spt dummy_harness_e2e` (BOTH tests: `endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches` + `endpoint_run_attach_awaits_online_before_attaching`) | 1× kitsubito (2026-07-05, REMOTE-TRUTH Batch-2 re-gate @f2a799e; roles swap run-to-run) | **process-global `set_var` race in a multi-test e2e binary.** Both tests `std::env::set_var(\"SPT_HOME\", own_tempdir)` then do IN-PROC reads (`perch::spt_home`/`adapters_dir`, `registry::register`, psychebin `fs::copy` staging). One file = one test b...\n*19|| 10 | `spt-store registry::tests::concurrent_registration_never_locks` | 1× hfenduleam local gate (2026-07-05, REMOTE-TRUTH Batch-2 bless matrix @3f6f327, fresh worktree, `nextest --workspace --no-fail-fast`) | 16-concurrent-writer SQLite registration gate FAILED at 5.63s — the SQLITE_BUSY load-contention class ALREADY documented at `db.rs:60`: box saturated by the 1588-test full-parallel suite meant a writer couldn't schedule its retry inside the default 5s `registry_busy_timeout`. Isolated ×5: gre...\n 20|| 11 | `spt-daemon::resume resume_mode_brain_spawning_new_sessions_delivers_each` | 1× kitsubito (2026-07-05 run 28768663321 attempt 1, REMOTE-TRUTH [twohost] PR #52) | Phase-B heavy-class timing EOF — \"session exited before the expected output arrived\" (UnexpectedEof) at 62s under the serialized heavy pool. SAME FAMILY as this file's prior Linux-CI flake (7940318, D4-2b: sequential spawn/read hardening) — a spawned session's exit raced the expected output read on the slow shared box. File untouched...\n*21|| 12 | `spt-daemon::input_ack_deadlock input_flood_through_serve_attach_does_not_deadlock_broker` | 1× hfenduleam local gate (2026-07-06, REMOTE-TRUTH F-2 matrix @a21bc6b, `nextest --workspace --no-fail-fast`) | deadline-margin under full-suite load: the flood exchange's `recv_timeout(30s)` (input_ack_deadlock.rs:512) blew under the 1608-test parallel pool + 7 leaky daemons — FAIL at 34.5s vs ~32s natural runtime isolated (the margin is ~3s by design). Subsystem disjoint from the triggering commit (F-...\n 22|| 13 | `spt::bin cli::tests::probe_all_cap_batches_into_windows` | 1× hfenduleam local gate (2026-07-06, stack matrix @0f08fa9 leg-3 rerun) | fast unit (0.52s natural) FAILED at 1.7s under the 1614-test full-parallel pool — probe-batching window assert with a timing component starved under load; subsystem disjoint from the triggering stack (F-2 reporting / E-2 grid title / picker rider). Isolated ×5: green, 0.52–0.55s. Same night as #12 — the full-parallel local matrix under a busy box is the com...\n*23|| 14 | `spt::bin rc::tests::attach_viewport_reconnects_across_a_broker_bounce` | 1× hfenduleam local gate (2026-07-06, WORKER-TRUTH W-3 build @worker-truth, `nextest -p spt --bin spt` full-parallel 360-test run) | **TIMEOUT, not an assertion fail** — SLOW>60…>TERMINATING at the 240s cap under the 360-way `--bin spt` pool. A REAL-broker + broker-bounce reconnect UNIT test (rc.rs:2071) that escaped the HEAVY-class serialization: the Phase-A/B split + `heavy-broker-pty` test-group target integration te...\n## GATEWAY-LIVENESS-DISPATCH.md\n 8|- **`spt endpoint list --json`** → **Suspended** (renders the endpoint via `resource_projection` over the registry snapshot, cli.rs:2855 → `EndpointRowJson.status` = the registry row status).\n*9|- flynn confirmed mobile-gw IS advertised in all 3 subnet registry files (NOT a re-advertise gap — his earlier \"empty by_id\" was a walker error, retracted).\n 10|\n 11|The adapter's suspend-honor poll reads `--json` (`parse_endpoint_status` over `endpoint list --json --show-all`) → sees Suspended → self-suspends the gateway, despite pid-alive + human-ONLINE.\n 12|\n## INJECT-SETTLE-REARM-GATE-VERDICT.md\n 5|## Scope\n*6|Post-0.29.0 field bug: PTY inject HEAD-TRUNCATION recurred on 0.29.0 — a checkpoint-wake payload injected right after `/clear` lost its head (perri screenshot `WindowsTerminal_6iSjya8pMt.png`). doyle root-cause + design ruling; todlando build.\n 7|\n 8|**Root:** `run_inject_worker` gated the W5-A Layer-1 settle-gate behind a worker-local ONE-SHOT (`settled_once`) on a FALSE premise (head-swallow race is startup-only). A mid-session `/clear` re-enters the harness raw-mode input reader → re-creates the pre-settle window, but the one-shot already fired at spawn → `settle_before_inject` skipped → head eaten again; echo-verify (Layer 2) default-OFF for that session → silent + unrecoverable.\n 9|\n*10|**Fix:** `settled_once` one-shot → `probe_unobservable` latch + pure `should_settle(attempt, probe_unobservable) = attempt>1 || !probe_unobservable`. Re-settle before EVERY delivery on an OBSERVABLE (echoing/interactive) PTY (bug-prone class, cheap); latch-skip the steady-state settle ONLY where the probe is UNOBSERVABLE (non-echoing ConPTY — no reattach race, full-deadline cost). The settle's own bool return discriminates the class, latched on attempt 1; a re-drive always settles. Mints REQ-HAZARD-I...\n 11|\n 12|## Diff review — FAITHFUL\n*13|Matches the design ruling exactly: latch semantics, pure gate, first-attempt-only latch update, re-drive-always-settles. KH 7.37 + traceable REQ minted with all three stages (`doc`/`impl`/`unit`) tagged on real evidence. RED-first unit `should_settle_rearms_on_observable_pty` present and correct. No int edit — safety-checked: `inject_control_wedge` mocks (`findstr`/`cat`) never answer the DSR probe → settle times out → `probe_unobservable` latches true after delivery 1 → identical to the old one-...\n 14|\n 15|## Both legs GREEN\n*16|- **Windows (ConPTY, isolated worktree + fresh target):** clippy `--workspace --all-targets -D warnings` = 0; `traceable-reqs check --json` = 0; nextest `--workspace` = **1707/1707** (9 leaky, 1 skip), exit 0. `should_settle_rearms_on_observable_pty` PASS.\n*17|- **Linux (kitsubito/forkpty, bundle-deploy @ddf7f8e):** clippy = 0; nextest `--workspace` = **1695/1695** (2 leaky, 1 skip), exit 0. `should_settle_rearms_on_observable_pty` PASS.\n 18|\n 19|## GATE-HOLD investigated → CLEARED (env contamination, not code)\n 20|First Windows full-suite run REDed on `inject_control_wedge::wan_arrival_to_idle_spt_hosted_injects_with_no_hook_poll` (`pending_spool_rows: left=1 right=0`, `INJECT_HEAD_LOSS_SPOOL` fired). Discriminated:\n 21|- Deterministic 5/5 isolated FAIL → not a flake.\n*22|- Root: `SPT_INJECT_VERIFY_ECHO=1` was exported in the gater's interactive live-agent shell (the live box arms Layer-2 echo-verify), inherited by the gate script → `echo_capable=true` → on Windows ConPTY the choreo child puts a headless line on the ring → Layer-2 false-positive head-loss → respool → `pending=1`.\n 23|- Proven env-driven: same test PASSES with `SPT_INJECT_VERIFY_ECHO` unset (deterministic both ways). Linux passed because its ssh env is clean.\n*24|- **CI-safe:** the var is **Process-scope only** (User/Machine scope empty) — the GitHub Actions runner service (hfenduleam, separate process tree) does NOT inherit it → PR CI runs clean. The clean-env full re-run (1707/1707) = CI's actual condition.\n 25|- The fix touches only Layer-1 settle; Layer-2 echo-verify is untouched W5-A code. The failure is orthogonal to the change.\n 26|\n 27|## Follow-up SEED (non-blocking; filed to todlando)\n## KNOWN-HAZARDS.md\n 70|### 2.5 Daemon-hosted endpoints have no dedicated liveness PID\n*71|- **Failure:** the sister evaluates Psyche/perch liveness via a dedicated process PID — the wrapper's own pid in `info.json`, checked with `is_process_alive`. Under ADR-0004 the Psyche (and any spt-hosted Self) is a **loop inside the daemon**, not a separate process: it holds no dedicated pid, and its `claude`/summarizer subprocess is ephemeral (spawned per pulse/commune, then exits). If a daemon-hosted perch's `info.json` carries the **daemon's** pid, then *every* hosted endpoint shares one pid, and `...\n 72|- **Invariant:** for **daemon-hosted** perches (Psyche; spt-hosted Self), liveness is the **daemon's authoritative in-memory endpoint table + a `status` field** on `info.json` (`online|offline|…`), **never** `is_process_alive(info.pid)`. `info.pid` for a daemon-hosted perch is at most a *hosted-by-daemon* marker (the daemon pid), not a liveness signal; registry stale-clean for these rows keys on the daemon's endpoint table, not per-row pid. This reuses the pattern already specified for **Shells** (`inf...\n 73|- **spt-core mapping:** the **M1/M2a interim** model keeps the Psyche/listener a real per-process owner (the `api listen` process), so its per-pid liveness (`deliver::is_online` → `info.read_pid` → `proc::is_process_alive`; `registry::clean_stale_entries`) is correct *interim*. **M3 daemon consolidation replaces it** with daemon-authoritative liveness for hosted perches. Keep the liveness check behind one resolver (mirrors `resolve_address` stale-clean) so the M3 swap is localized — do **not** let ...\n 74|- **Sister cite:** `src/live/wrapper/orphan.rs` (wrapper-pid liveness); `src/common/list_filter.rs:168-175` (pid-classify); spt-core `crates/spt-store/src/{proc.rs,registry.rs}` + `crates/spt-msg/src/deliver.rs::is_online`.\n...\n 80|### 3.1 Ephemeral perch cleanup on every `ring` exit path\n*81|- **Failure:** `ring` creates an ephemeral perch; early-exit paths (no-perch, empty-msg, timeout) skip cleanup → stale dirs accumulate.\n 82|- **Invariant:** every code path that creates an ephemeral perch cleans it before exit; exception: if the caller already had an active perch, do not treat as ephemeral and do not clean up.\n 83|- **spt-core mapping:** `ring` semantics carry; the daemon owns ephemeral-perch lifecycle, so a single guaranteed-cleanup (drop guard / RAII) is achievable in-process.\n 84|- **Sister cite:** `src/owl/ring.rs:58-294`.\n...\n 199|\n*200|### 5.5 ConPTY withholds output until DSR is answered  `[REQ-HAZARD-CONPTY-DSR]`\n*201|- **Failure:** a broker reading a ConPTY master sees only the 4-byte startup query `ESC [ 6 n` and then nothing — the child looks hung/silent but is producing output normally. ConPTY blocks all child stdout until the terminal answers the cursor-position query.\n*202|- **Invariant:** every ConPTY reader auto-answers DSR (`ESC [ 6 n` → write `ESC [ 1;1 R`, or a real cursor position) on the PTY writer. Secondary: a ConPTY master does not EOF while the writer is held, so read loops drain on a thread and never gate exit on a blocking `read()`.\n*203|- **spt-core mapping:** `spt-term` broker PTY reader (ADR-0004). Brand-new to spt-core — not in the sister project (it never hosted ConPTY directly).\n 204|- **Source:** Spike #1 (`docs/spikes/SPIKE-01-broker-handoff.md`); reproduced with both a Rust child and `cmd.exe`.\n 205|\n 206|<!-- [doc->REQ-HAZARD-DETACHED-PIPE-INHERIT] -->\n...\n 249|\n*250|<!-- [doc->REQ-HAZARD-WIN-PTY-PROGRAM-RESOLVE] -->\n*251|### 5.12 Native-PTY spawn of a bare program runs the wrong (non-PE) file on Windows  `[REQ-HAZARD-WIN-PTY-PROGRAM-RESOLVE]`\n*252|- **Failure:** `portable-pty`'s ConPTY spawn resolves a bare program name with a `which` that takes the FIRST `PATH` match. A node/npm CLI installs as BOTH an extensionless shebang shim (`ccs`, for Git Bash) and a Windows launcher (`ccs.cmd`) in the same dir; portable-pty picks the extensionless `ccs`, and `CreateProcessW` then tries to execute that non-PE file and fails with **os error 193** (\"%1 is not a valid Win32 application\"). Live failure: `spt endpoint run claude-spt:ccs` → `CreateProcessW C:\\n...\n*253|- **Invariant:** spt-term resolves the program ITSELF before handing it to `CommandBuilder`, bypassing portable-pty's `which`. A bare name is searched over `PATH` × `PATHEXT` (whose default order already prefers `.EXE`/`.COM` over `.BAT`/`.CMD`), then an extensionless fallback. A non-PE target is wrapped in its interpreter: `.cmd`/`.bat` → `cmd.exe /d /c <path>`, `.ps1` → `powershell -NoProfile -File <path>` (the wrap args precede the caller's args); a real executable spawns directly; an unresolvabl...\n*254|- **spt-core mapping:** `spt_term::winprog::{resolve_for_pty, resolve_in}` (the pure PATHEXT-precedence kernel + the Windows env wiring), wired into `spt_term::pty::PtySession::spawn_program_in`. Unit: `resolve_in` precedence (`.cmd`-over-shim, `.exe`-direct, explicit-extension, path-order, passthrough) [`winprog.rs`].\n 255|- **Source:** field diagnosis 2026-06-16 (operator dogfood, `claude-spt:ccs` bringup) — doyle.\n 256|\n 257|<!-- [doc->REQ-HAZARD-PERCH-RECORD-POWER-LOSS] -->\n...\n 300|\n*301|### 6.3 Cascade-wipe guard: never delete a parent hosting non-empty children\n 302|- **Failure:** `doctor --fix` deletes a top-level perch that still hosts in-flight nested Worker/Psyche perches.\n*303|- **Invariant:** before hard-delete, check for non-empty nested children; if present, soft-clean only and surface the path.\n 304|- **spt-core mapping:** any destructive maintenance command must check for live child instances first.\n 305|- **Sister cite:** CHANGELOG v1.11.20 Phase 35.1.\n 306|\n...\n 325|### 6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`\n*326|- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old c...\n*327|- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/0...\n 328|- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.\n 329|- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.\n 330|<!-- [doc->REQ-HAZARD-BROKER-PROCESS-ISOLATION] -->\n...\n 346|- **Invariant:** the daemon-hosted multi-session event loop must not consume a session's OUTPUT inside another session's blocking wait. The **live-agent adapter milestone must restructure the brain event pump** so command/`spawn` is non-blocking (a single demux loop owns `read_event` and routes every frame to its session's consumer), OR a blocking wait must re-queue/route the frames it reads for other sessions rather than dropping them.\n*347|- **spt-core mapping:** **unreachable today** — the supervised daemon brain hosts no PTY sessions and spawns none; a single-session seat (legacy, empty map) has no \"other session\" to starve. Surfaces the moment daemon-hosted sessions land (the live-agent adapter), which must rebuild the blocking `read_event` loop regardless (N interactive sessions cannot share one blocking reader). Recorded so that redesign inherits the constraint rather than rediscovering it. No machinery now (would be untested dead c...\n 348|- **Origin:** surfaced by the D4-2b resume-harness CI flake root-cause (agents `todlando` + `doyle`, 2026-06-10); sibling of `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]` 6.7.\n 349|\n 350|### 6.10 Phase-significant loop timing must be a durable absolute-deadline grid, not phase-relative sleep `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`\n...\n 375|### 7.2 Idempotent delivery across brain restart  `[REQ-HAZARD-RESTART-IDEMPOTENT]`\n*376|- **Failure:** broker queues, spool rows, PTY injection, remote streams, and file transfers cross the broker↔brain boundary; a brain restart duplicates or drops a side effect.\n*377|- **Invariant:** every side effect crossing the boundary carries a durable ID + replay rule; replay after restart is exactly-once / idempotent. Crash the brain before/after spool write, before/after PTY write, mid-transfer, mid-registry update — no dup, no drop.\n 378|- **spt-core mapping:** broker↔brain IPC (ADR-0004), self-update handoff.\n 379|- **Source:** codex Stage A #14.\n 380|\n...\n 403|- **Failure:** the peer pump is a SINGLE thread driving every leg (registry/notif/sync/update) against every peer over ONE brain-IPC client. Its reply reads (`net_open_stream`, `net_stream_send`, `net_dial`, and the sync/update pull `read_event` loops) were `loop { read_event() }` with no deadline. When a peer's QUIC path black-holes, the broker's stream-open/send awaits the dead peer and never sends the reply, so the brain's `read_frame` blocks FOREVER and the pump freezes mid-round. The heartbeat (loop...\n*404|- **Invariant:** in PUMP mode the brain carrier is **SPLIT at construction** — a dedicated `pump-ipc-reader` thread does blocking `read_frame` on the `RecvHalf` and forwards each framed result down a channel; the main thread writes on the `SendHalf` and reads with `Receiver::recv_timeout`. Every IPC reply read is bounded by a per-call **total-wait** deadline (`PUMP_PEER_IO_TIMEOUT` = 30s, > any legitimate round-trip, < the 60s QUIC idle; re-armed on stream progress for the streaming pull legs so a heal...\n 405|- **spt-core mapping:** `Brain::cold_start_pump` (splits the carrier + arms the deadline) / `BrainConn::Split` (the `SendHalf` + reader-thread channel) / `call_deadline` / `read_event_until` / `read_frame_until` (the `recv_timeout` dispatch), `pump::run_peer_pump` (connects in pump mode) + `pump::peer_outcome` (the tier-split), the `request_sync`/`request_update` pull loops (deadline re-armed on progress). The broker-side half — the broker must never make a brain wait unbounded on a QUIC op (bound the ...\n 406|- **Source:** field diagnosis 2026-06-11 (the 2.2h hfenduleam wedge); doyle ruling A-now / B-deferred. The stall warning (M8 decision 23) was the band-aid; this is the fix. The B-half landed v0.8.3 (§7.8) after the 2026-06-16 recurrence.\n 407|\n...\n 409|<!-- [doc->REQ-HAZARD-VIEWER-ISOLATION] -->\n*410|- **Failure:** the W2.5 controller/viewer model lets ANY number of read-only `--view` attachers ride one session's broker `OutputLog`. The single drain thread fans each output chunk to every attacher. If a viewer's socket is fanned out with a **blocking** write under the log lock (the controller's authoritative path), one wedged viewer (a slow terminal, a black-holed WAN peer, a hostile non-reader) stalls the drain — freezing the controller's stream and backing up the PTY child. A single watcher must n...\n 411|- **Invariant:** the drain writes the **controller** on the authoritative blocking bounded path (it alone advances `delivered_through`), but each **viewer** gets an **isolated bounded SPSC queue + a dedicated writer thread**; the drain `try_send`s under the log lock and **evicts** any viewer whose queue is `Full` (fell behind the live stream) or `Disconnected` (its writer died on a dead socket) — the drain thread **never touches a viewer socket**, so no viewer write can backpressure it. A **soft cap** ...\n 412|- **spt-core mapping:** `OutputLog::append` (controller blocking + `viewer_send_evicts` `try_send` fan-out), `OutputLog::add_viewer` (bounded `sync_channel(VIEWER_CHANNEL_DEPTH)` + `viewer_writer` thread), `MAX_VIEWERS` soft cap, `ViewerSink`. Unit: `viewer_overflow_or_disconnect_evicts_never_blocks`. Int: `wedged_viewer_does_not_stall_controller` (a non-reading viewer is evicted while the controller keeps receiving past a 200KB burst).\n 413|- **Source:** M12 W2.5 controller/viewer model (doyle ruling 2026-06-14, Q1).\n## MANIFEST.md\n 129|<!-- [doc->REQ-PSYCHE-CONTEXT-FILE-INDIRECTION] -->\n*130|**Psyche-download — `{psyche_context_file}` (file-always, replaces `{psyche_context}`).** The composed Psyche mind rides a **file**, never the command argv: before each turn spt-core writes the mind into the nested psyche perch dir and fills a single **`{psyche_context_file}` = that path** (argv-cap-immune — a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / re...\n 131|\n 132|<!-- [doc->REQ-PSYCHE-NESTED-RESOLUTION] -->\n\n[Showing lines 1-276 of 531 (49.9KB limit). Use :277 to continue. Some lines truncated to 512 chars]"}],"details":{"scopePath":"docs, CHANGELOG.md, README.md, src, tests","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":113,"fileCount":20,"files":["docs/BROKER-BRAIN-SPLIT-RESTORATION.md","docs/CONTEXT-MEMORY.md","docs/DEFERRED.md","docs/DORMANCY-BUDGET.md","docs/F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md","docs/F-030-PSYCHE-EPHEMERAL-DESIGN.md","docs/F-035-CONFLICT.md","docs/FAULT-MATRIX.md","docs/FLAKE-LEDGER.md","docs/GATEWAY-LIVENESS-DISPATCH.md","docs/INJECT-SETTLE-REARM-GATE-VERDICT.md","docs/KNOWN-HAZARDS.md","docs/MANIFEST.md","docs/MESH-RECOVERY-TRIAGE.md","docs/MSG-IDENTITY-DISPATCH.md","docs/MUSL-W3-DISPATCH-RULING.md","docs/NEXT-MILESTONE-BUG-TRIAGE.md","docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md","docs/NEXT-MILESTONE-MUSL-TIER-TRIAGE.md","docs/NEXT-MILESTONE-PICKER-POLISH-TRIAGE.md"],"fileMatches":[{"path":"docs/BROKER-BRAIN-SPLIT-RESTORATION.md","count":6},{"path":"docs/CONTEXT-MEMORY.md","count":1},{"path":"docs/DEFERRED.md","count":4},{"path":"docs/DORMANCY-BUDGET.md","count":4},{"path":"docs/F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md","count":2},{"path":"docs/F-030-PSYCHE-EPHEMERAL-DESIGN.md","count":2},{"path":"docs/F-035-CONFLICT.md","count":1},{"path":"docs/FAULT-MATRIX.md","count":11},{"path":"docs/FLAKE-LEDGER.md","count":5},{"path":"docs/GATEWAY-LIVENESS-DISPATCH.md","count":1},{"path":"docs/INJECT-SETTLE-REARM-GATE-VERDICT.md","count":7},{"path":"docs/KNOWN-HAZARDS.md","count":20},{"path":"docs/MANIFEST.md","count":20},{"path":"docs/MESH-RECOVERY-TRIAGE.md","count":1},{"path":"docs/MSG-IDENTITY-DISPATCH.md","count":1},{"path":"docs/MUSL-W3-DISPATCH-RULING.md","count":1},{"path":"docs/NEXT-MILESTONE-BUG-TRIAGE.md","count":18},{"path":"docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md","count":4},{"path":"docs/NEXT-MILESTONE-MUSL-TIER-TRIAGE.md","count":1},{"path":"docs/NEXT-MILESTONE-PICKER-POLISH-TRIAGE.md","count":3}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# docs/\n## BROKER-BRAIN-SPLIT-RESTORATION.md\n  50│\n *51│- **broker** (stable kernel) — holds *only* the un-transferable, must-not-die resources: PTY master fds, spawned harness child processes, listening network sockets, and (per the §B ownership table) the Iroh/QUIC endpoint + conn table. Minimal, versioned local IPC. Almost never updates.\n  52│- **daemon brain** (userspace) — all logic (routing, registry, pulse/psyche loops, manifest parsing, update orchestration). Restarts freely on update; rehydrates from disk and re-attaches to broker-held handles.\n  53│\n  54│Explicit evidence the split was meant to be a **process** boundary:\n    │...\n  57│- `M3a-PLAN.md`, `M3-PLAN.md`, `M3b-PLAN.md` all name \"the broker **process** + IPC (M3b).\"\n *58│- Spikes 01/03/04/05/06 each **PROVEN** with *two separate binaries* (`spt-spikes/spike-01-broker-handoff`): a PTY child + a live QUIC transfer survive 100× brain restarts, gapless and exactly-once. A spike that proves \"survives a brain restart\" presupposes the broker is a separate process.\n  59│\n  60│### 2.2 What production actually does\n  61│\n    │...\n  72│\n *73│- The brain cannot restart onto a new binary without killing the in-process broker thread — which would close every PTY, orphan every harness child, and drop every listening socket. So **the no-endpoint-drop self-update pillar is silently unrealized.**\n  74│- `apply` therefore performs an in-process `Brain::handoff` that re-attaches a subscriber within the *same old process* — achieving nothing for a binary swap. `update.rs:234`'s statement that \"the live daemon execs the verified new binary\" is **aspirational; it was never wired** because the process split it depends on does not exist in production.\n  75│- Updates do not run until an unrelated restart/logon (observed live, Section 1).\n  76│\n    │...\n 182│\n*183│**Decision/observation.** The broker spawns the brain as a **child process** (`Command::spawn`) and they communicate over the existing local socket IPC. **No `exec` is required**, so there is no Windows/Unix divergence. ConPTY (Windows) and forkpty (Linux) handles stay broker-side and never cross to the brain. Spikes 01 and 04 already proved the handoff on both OSes.\n 184│\n 185│**Reason.** This is precisely the uniformity ADR-0004:25 sought when it rejected whole-daemon live FD-passing as \"hard and platform-divergent.\" Spawn-plus-socket sidesteps it entirely.\n 186│\n    │...\n 227│- **Glossary/model** — `CONTEXT.md` daemon / broker / brain (incl. the one-broker-per-machine clause added this session).\n*228│- **Spikes** — `docs/spikes/SPIKE-01-broker-handoff.md`, `-03-quic-survival`, `-04-forkpty-parity`, `-05-restart-stress`, `-06-idempotent-boundary` (proofs that the two-process handoff works on both OSes).\n 229│- **Key source** — `daemon.rs` (`run`, net consumers, self-heal, digest hub, shellwake, seed-control), `broker.rs` (`OutputLog`, `bind`, `EffectJournal`), `brain.rs` (`BrainState`, `cold_start`/`handoff`/`snapshot`), `applyhost.rs` (`apply_staged`, `connect_retry`, record-before-handoff), `update.rs` (classes, `classify`, `apply_brain_only`), `peerloop.rs` (cadences, supervise-backoff), `lifecycle.rs` (pulse loop), `registryhost.rs`.\n 230│\n 231│---\n    │...\n 238│- **`REQ-*` registry mints** (registry-first, `traceable-reqs.toml`; minted INACTIVE per rule 5, activated per `RESTORATION-PLAN.md`'s schedule):\n*239│  - **`REQ-HAZARD-BROKER-PROCESS-ISOLATION`** — a brain restart must never drop a hosted endpoint (REQ-UPD-3's real promise). **Evidence [V5]:** an `int` stage productionizing SPIKE-01/03 — a PTY child + a live QUIC conn survive a brain-*process* restart onto a *swapped* binary. REQ-UPD-3 / REQ-DAEMON-2's current `int` evidence (`applyhost.rs:39/77`, `update.rs:246`, `brain_swap.rs`) proves only the *in-process* handoff shape — **mis-evidenced today, re-pointed at plan task D7.**\n 240│  - **`REQ-HAZARD-ROLLBACK-STATE-COMPAT` [V1]** — a brain must not irreversibly migrate durable state before ready-promotion (pre-ready writes stay N-1-readable).\n 241│  - The functional two-process-split realization + the **[V6]** N-1 old-broker × new-brain CI compat test are carried as `RESTORATION-PLAN.md` tasks D1–D7 (the compat test lands as a D7 gate).\n 242│- **KNOWN-HAZARDS** §6.7 (in-process collapse) + §6.8 (rollback-state-compat), with condensed-checklist rows.\n## CONTEXT-MEMORY.md\n 36│\n*37│A lever for an agent behind an **spt-hosted** endpoint to clear and resume itself without stalling. It: (1) triggers a `/clear` (which uses the **Self-resume-commune rule** above — consistent with all other boundaries), (2) thereby captures a commune, and (3) **guarantees a resume signal hits the harness post-clear** so the agent's turn restarts and it resumes work — starting from its immediate next-steps. Spt-hosted-only because the guarantee requires the daemon to own the PTY and inject the post-cl...\n 38│\n 39│### Ownership\n 40│\n## DEFERRED.md\n 11│| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1. **M5 ships presence *resolution* (gossip + most-recently-active API) — its future substrate; the endpoint stays deferred** (user decision 2026-06-04). <!-- [doc->REQ-PRES-1] --> **M5-D6 seam note:** the substrate now exists — the gossiped datum (`Instance.last_active_ms`, riding the registry's epoch lease + replication) and the one...\n*12│| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal. **Note:** distinct from the v1 broker *sensing* user input on a PTY it already holds (spt-hosted) — that is in v1 and is NOT OS-wide input monitoring | Opt-in enhancement after agent-interaction presence proves out |\n 13│| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works. Consent *model* is now designed (CONTEXT §Consent & security gates); **the framework (grant store + escalation + pre-consent flags) ships M5 with this capability id reserved-but-refusing** (user decision 2026-06-04) — the capability lands into it later. Defers with it: the remote-fork arm, cross-node shell spawn, `shell_wake_spawn_anywhere` *behavior* (grant s...\n 14│| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly). **M5 ships the framework with this capability id reserved-but-refusing** (user decision 2026-06-04) | Designed alongside instantiate-anywhere's consent gate |\n 15│| Shell-binary (and harness-binary) sandboxing | Shell concept v1 | A capability toolset bounds what the *agent* can ask, not what the *binary* can do with its OS perms. Baseline stance: running any adapter/shell binary = a disclosed, accepted trust risk for all spt-core users | If/when untrusted third-party shell/harness binaries become common enough to warrant OS-level sandboxing |\n   │...\n 26│| Trust-row prune verb (`spt subnet prune <node>` or similar): registry GHOST rows now decay (4.10), but the dead identity's TRUST rows persist by design (trust is a user decision) — they cost dead dials every pump tick and clutter status views. BIGNET carries two such rows (09ef831e, both sides) | Ghost-eviction fix (2026-06-06) | Trust mutation = security surface; wants the elevation gate + the M8 noun shapes | M8 CLI nounification |\n*27│| Elevated spt-hosted endpoints — a consented elevation satellite: the broker lives inside the always-unelevated daemon (KH 5.7), so spt-hosted children are unelevated by construction; there is NO silent unelevated→elevated path on either OS (that is UAC/sudo's whole point). An agent needing admin powers is harness-hosted-elevated today (works: TCP listeners + talk-down pipes). The spt-hosted form needs a per-grant CONSENTED elevation: UAC prompt / polkit spawns a small elevated PTY-host satellite ho...\n 28│| ~~AMBIGUOUS render~~ **SHIPPED 2026-06-06** (rode the ghost-eviction fix): resolution refusals render valid copy-paste targets at the wansend boundary (`render_refusal`) — labels preferred, key-prefix on label collision, `subnet:id` for cross-subnet | M7 acceptance run (user 2026-06-06) | Pure render fix at the wansend/CLI boundary; carries into the M8 nounification sweep | DONE |\n 29│| Post-join address seeding: the pairing ceremony holds a live, authenticated connection to the peer, but the peer pumps then re-find each other from scratch via id-only discovery (fresh node ids must propagate to n0 DNS first) — observed live as ~1 min to first `status --nodes` convergence after a join. Seed the pump's address cache from the ceremony connection (both sides) for instant first sync | M7 acceptance run (2026-06-06) | Convergence is correct, just slow on first contact; needs an addr-cache...\n 30│| Clock source priority for TOTP/rendezvous: query a well-known world clock (NTP) FIRST, fall back to local time — machines with skewed local clocks currently derive wrong steps/rendezvous tokens silently | M7 acceptance grill (user 2026-06-06) | Needs an NTP client dep + offset caching + offline fallback semantics; ±1 window covers small skew today | First field report of clock-skew join failures, or M8 polish |\n   │...\n 43│| **Durable in-daemon alarm scheduler (one-shot deadline machinery — the Q4/V3 deferral)**: the broker/brain restoration (ADR-0018 Q4) fixes the *rule* for one-shot scheduled events — persist the absolute `target-time` at creation; every brain start (update *or* crash) reads it and fires-if-due; **never reset on crash** (a user's \"remind me at 3pm\" is a commitment that must outlive any restart). But it deliberately does **not** build the machinery: the spt-core daemon has **no one-shot consumer today...\n*44│| **Autonomous session-digest freshness + Option-C re-home (M9 ADR-0008 amendment deferral)**: M9 re-founded the digest as an ON-DEMAND projection of normalized session logs (`spt-term::projection`; `spt-daemon::digest::project_endpoint_digest`) — snapshot-pull and the structured-delta-stream CONTRACT ship, deltas driven by pulls / `api digest-entry` pushes. Two pieces deferred, both because they have **no consumer yet** (Shell=M10, GUI/frontend pane, Gateway agent-window — none exist): (a) the **aut...\n 45│| **Two-origin `owl_message` context-tap subtype refinement (M10 ADR-0019 deferral)**: the two-origin merge's owl-message producer (`spt::api::startup` `deliver` closure) taps the live-agent **`emit` chokepoint**, so EVERY delivered frame — including resurfaced **notif** frames and shell-context rows — is recorded as a single coarse `owl_message` context-injection entry (`context_kind:\"owl_message\"`). The data is not wrong (a notif IS injected context), only coarsely labeled; the digest is a glanceab...\n*46│| **W2.5 — attached-presence + Kick-and-attach (M12 picker blue tri-state)**: M12 W2's picker ships online/offline status only; the **blue ■ \"attached\" tri-state** + the **\"Kick `<node>` and attach\"** confirm option were SCHEDULED (operator 2026-06-14, NOT dropped) as a dedicated slice built AFTER W2 lands, then wired back into the picker. Scope: (1) a **broker attach-presence query** — \"who is attached to endpoint E's PTY, by which node/surface\" — derive from the `presence.rs` PresenceLog conn c...\n 47│| **Cross-node shell TUNNEL relay (REQ-SHELL-4 cross-node-on-LAN carry)**: W3 built + proved the opaque byte tunnel **same-node only** (broker-homed `TunnelHub` loopback stream pair + the local control socket; `tunnel_e2e` round-trips `<EVENT`-looking bytes byte-exact). The **cross-node** path does NOT exist: `StreamFamily` (dispatch.rs) has no `Tunnel` variant, `classify_first_line` no tunnel arm, `SHELL_LINK` carries only relink/cmd/drive (no tunnel action), and every tunnel surface (`tunnel_ensure/sen...\n 48│| **Per-connection contiguous-sent cursor — structural guard against a decreasing-floor controller re-take (`REQ-HAZARD-CONTROLLER-WRITER-REORDER` follow-up)**: v0.13.0 P1c fixes the controller-writer reorder while KEEPING handoff's eager subscribe (fix #1 \"drop the subscribe\" was reverted — it's the standalone-resume mechanism), so the decreasing-floor double-take (handoff subscribe@K + serve_attach subscribe@0) is still PRESENT and made safe by: `controller_writer` epoch-gated under `send.lock()` (...\n 49│| **Adapter-manifest-enabled rc VT/mouse mode (`spt rc` scroll vs right-click-paste xor resolution)**: v0.13.0 P1/P1b's Windows `EnableMouseCapture` (for client-originated right-click bracketed paste, 7.18) makes Windows Terminal hand ALL wheel events to rc instead of scrolling its own scrollback; rc forwards the wheel to the harness as SGR reports (7.20) but ONLY when the harness has mouse-reporting on (`enabled && sgr`) — and **Claude Code's REPL never enables mouse tracking** (it has no internal scr...\n## DORMANCY-BUDGET.md\n 15│\n*16│1. binds a real (net-less) broker — the production PTY-hosting path — in a\n 17│   hermetic `SPT_HOME`;\n 18│2. spawns N idle seats through `Brain::spawn_session_pid` (one probe brain per\n 19│   seat, each labeled `budget-<i>`), exactly the shape of a **dormant** seat:\n*20│   PTY master + harness child + output ring, state-preserved and undriven;\n 21│3. settles, then samples each child's **RSS**, **handle/fd count**, and\n 22│   **cumulative CPU** twice across an idle window — the CPU delta is the idle\n 23│   burn of a warm seat;\n 24│4. samples the host process's RSS before/after spawning (the broker-side\n*25│   per-seat overhead: PTY plumbing + output rings);\n 26│5. kills every seat (the **suspended** state — only the on-disk perch record\n 27│   would remain in production) and samples the residual.\n 28│\n   │...\n 31│count, and `/proc/<pid>/stat` utime+stime. **Limitation:** the sample covers\n*32│the direct PTY child only — a harness that forks a worker tree under-reports\n 33│by the tree's cost (the claude numbers below are the main node process, which\n 34│dominates).\n 35│\n## F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md\n  46│| 5 | STALE-CLI-CONTRACT — bringup recipe passes a removed flag; \"unexpected argument\"; spawn never happens | old recipe on 0.22.0+ | LOUD pre-spawn (good shape) |\n *47│| 6 | RELAUNCH-NEVER-BINDS — 2nd `endpoint run` same id after tree-kill; prints but never binds | **0.22.0 only — FIXED on 0.23.0 by F-028 B1/B4** (perri definitive repro 2026-07-03: tree-kill → same-id re-run re-binds fresh session, live PTY, honest info.json; first check was rig-void, --subnet omitted, caught before reporting) | FIXED-NOTE (not in F-027 scope) |\n  48│| — | UAC-740 elevation spawn-fail | NOT observed (subnet-op elevation only) | out of scope |\n  49│\n  50│Seed-1 correction (accepted): the \"backslash-keyed `hasTrustDialogAccepted`\n    │...\n 212│3. **Mode-6 — ANSWERED: FIXED** by F-028 B1/B4, 0.23.0 definitive repro\n*213│   (same-id re-run after tree-kill re-binds fresh session, live PTY, honest\n 214│   info.json). Provenance table updated; out of F-027 scope.\n 215│4. **Env capture-at-creation — ANSWERED: CONFIRMED**, promoted from open\n 216│   question to the §3 Half-B binding invariant: `[env] direction=\"read\"` vars\n## F-030-PSYCHE-EPHEMERAL-DESIGN.md\n  70│| 2 | hall-bf-psyche boot-churn ~5 s cadence, ordinal 6491+, synthetic `sess-<pid>-<ts>` ids, no real session ever binds; starts exactly at BIGNET join (7/3) | operator box, real home | adapter can't hold residency |\n *71│| 3 | Poll-verb root repro: `spt ready <id> --once` on 2-subnet home → exit 1, `READY_FAIL … pass --subnet`, empty stdout | perri, clean repro | adapter can't hold residency |\n  72│| 4 | v0.13.2 shim-exit → residency teardown → ready removed → permanent `leg=cli-gate-not-hosted` | hall-bf + perri wake-confirm | residency expectation destructive |\n  73│| 5 | Psyche turns land fine via daemon-driven paths while the resident churns | hall-bf digest (`psyche_download` at every boundary) | per-event shape already proven |\n  74│| 6 | ~12/min re-host cadence NEVER tripped the C3(b) thrash guard (boot records are apparently not ledger boundaries to the guard) | hall-bf | guard blindness (rides this wave) |\n    │...\n 120│  role/live/project into the psyche-download file whose path fills\n*121│  `{psyche_context_file}` (file-always; non-empty content = fresh preload,\n 122│  0-byte = continue — replaces the former `{psyche_context}` body key) and a\n 123│  fresh psyche session seeds from it. Loud marker on reseed (`PSYCHE_RESEED:{id}`) so a\n 124│  custody-loss loop is visible, plus the W1 failure budget bounds it.\n## F-035-CONFLICT.md\n 34│\n*35│For an **spt-hosted** endpoint (broker owns the PTY, `controllable`, relay-less), the\n 36│idle-edge/parked drain is the **ONLY** carrier for an `active_only` row. Traced every path:\n 37│\n 38│| carrier | `active_only` to spt-hosted? |\n## FAULT-MATRIX.md\n  3│**Why this exists.** ADR-0002 collapsed networking into the one per-machine daemon\n *4│(SERIOUS #6, Stage-A red team): a networking bug and a PTY bug now share a failure\n  5│domain. The collapse is locked — the owed mitigation is this matrix: inject a fault\n  6│into each subsystem **independently**, observe the blast radius (what else breaks)\n  7│and the recovery (what brings it back), and grow the proven rows until every\n   │...\n 19│|---|---|---|---|---|---|\n*20│| 1 | **Brain killed mid-PTY-stream** (logic crash / routine self-update) | Logic halts until restart | Broker, PTY child (pid stable), output log | New brain re-subscribes from cursor; gapless + exactly-once | `spt-daemon/tests/` B2/B9 handoff suite; `tests/idempotent.rs` (crash before-intent / before-effect / after-effect) |\n 21│| 2 | **Brain killed mid-QUIC-stream, receive side** | Logic halts | Broker-owned endpoint, conn, stream, read ring | Resubscribe from durable cursor; ring replays the dead window | `tests/netstream.rs::receiver_brain_restart_is_gapless_and_exactly_once` |\n 22│| 3 | **Brain killed mid-QUIC-stream, send side** | Logic halts | Broker-owned conn/stream + effect journal | Re-drive whole durable op sequence; journal dedups replays (no dup on the wire) | `tests/netstream.rs::sender_brain_restart_redrive_is_exactly_once` |\n 23│| 4 | **Brain dead across peer connect/disconnect** | Presence consumption pauses | PresenceLog ring buffers liveness transitions | Resubscribe from presence cursor; missed transitions replay | `tests/presence.rs` (both tests) |\n   │...\n 26│| 7 | **Registry feed: corrupt record in the stream** | That record only — skipped | The rest of the feed (decoder does not wedge) | Lease makes loss safe; next update supersedes | `replicate.rs::decoder_survives_chunk_splits_and_corrupt_lines` |\n*27│| 8 | **Net disabled / endpoint bind fails at boot** | No WAN; net frames answer `enabled:false` / typed error | PTY hosting (daemon degrades to net-less broker) | Fix config, restart daemon | `broker.rs::dispatch_net_status` (None arm); `Daemon::run` degrade path |\n*28│| 9 | **Broker process restart (journal survives, conns don't)** | Live conns/streams lost | Effect journal (durable) | Deduped op whose resource is gone → typed \"retry with a fresh op_id\" error; brain re-dials; an `spt rc` operator viewport RECONNECTS on its own — centered banner, full establish re-drive inside a bounded window, then a plain-language give-up (REQ-RC-RECONNECT) <!-- [doc->REQ-RC-RECONNECT] --> | `broker.rs::dispatch_net_dial` / `dispatch_net_stream_open` restart arms; `rc.rs` reconne...\n 29│| 10 | **WAN msg feed replayed** (sender redrive, or receiver brain restart resubscribing from a stale cursor) | None — replayed records dedup | Spool state (each op exactly one row); already-delivered ops | Durable `wan_seen` op-id claims absorb the replay; spool path claims atomically with the row | `tests/wanmsg.rs::receiver_restart_replays_feed_without_double_delivery`; `spool.rs` wan unit tests |\n 30│| 11 | **WAN msg record forges its origin** (payload `origin_node`/`node` field) | None — forgery inert (unknown field) | Access-gate subject (handshake-proven `remote_id_hex`) | None needed (records carry no origin field by design — KH 7.5) | `tests/wanmsg.rs::wan_message_lands_exactly_once_under_transport_origin`; `wanmsg.rs::forged_origin_field_is_inert` |\n*31│| 12 | **Target brain killed mid-remote-attach** (operator typing into the dead window) | Pumping pauses until handoff | Broker-held QUIC stream, session + child, output log, effect journal; the operator's viewport | Successor re-serves the same stream from seq 0 (worst case): replayed input dedups at the PTY-write journal; re-transmitted output dedups at the operator's render cursor — every byte exactly once | `tests/attach.rs::attach_survives_target_brain_restart_exactly_once` |\n 32│| 13 | **Target brain killed mid-file-transfer** (push receive: partial temp file on disk) | Transfer pauses until handoff | Broker-held QUIC stream + ring, the partial temp, the durable progress record (last observed position) | Successor re-serves the same stream from seq 0 (worst case full replay): chunks carry absolute offsets — replayed bytes rewrite in place; the commit is an atomic temp→final rename gated on temp completeness, and a replayed commit dedups against an already-committed final (ne...\n 33│| 14 | **Stream lost mid-context-bundle-sync** (responder dies / wire tears before `Done`) | That one pull errors at the requester | The requester's context store (an incomplete bundle is never fetched — length-gated, scratch file deleted); the responder's store untouched | No resume protocol by design: re-pull fresh — the apply is ancestry-idempotent (an already-joined tip short-circuits; fetched tips quarantine under `refs/spt-sync/` so a partial fetch never clobbers `refs/heads/`) | `tests/sync.rs...\n 34│| 15 | **Partition: concurrent context writes on two nodes** | The file's auto-propagation pauses (conflict surfaced, not propagated) | BOTH versions — local file untouched on each node + the other's version as a durable tracked artifact (hazard 6.6); every other file keeps syncing | Elected reconciler (Active instance's node; lowest-id fallback) runs one bounded Psyche turn → merged write `join(vA,vB)+bump` dominates both parents → propagates as a plain accept, clears artifacts subnet-wide | `test...\n*35│| 16 | **Reconcile turn fails** (harness absent / timeout / garbage or empty output / 6.5-suppressed write) | That file stays in conflict-holding state | Both versions (artifacts + local file) — exactly where they were; the store, the sync loop | Retry at the next sync/activation; a fresher direct write re-merges against the new local version | `spt-live reconcile.rs::failed_turn_preserves_everything` / `suppressed_write_preserves_artifacts`; `turn.rs::empty_stdout_is_an_error` |\n 36│| 17 | **Registry ambiguity during reconciler election** (partition: two nodes both see Active / no Active anywhere) | Two textually-different merges of the same pair may both mint | Neither version is ever lost — merged writes carry joined vectors, so the two merges classify **concurrent** and re-surface as a new conflict pair (detected, not silently last-wins) | Deterministic tiebreak bounds the storm (lowest node id among Actives / non-Offline holders); the re-surfaced pair reconciles on the next pa...\n 37│| 18 | **Compromised relay node serves a tampered update artifact** (bytes corrupted at rest after the relay's own verification) | That one pull ends `Rejected(ArtifactMismatch)` at the puller | The puller's staged release + running binary (nothing staged, nothing applied — no code path from wire bytes to the cache except through `plan_verified`); the rest of the subnet pulls from honest peers | Pull from another peer; the per-node gate (REQ-UPD-2) runs at **every** hop, so one poisoned node never re-p...\n 38│| 19 | **Non-conforming peer offers a rollback/expired/off-channel release** | None — rejected pre-fetch, zero artifact bytes move | The puller's version (monotonic floor holds); the wire (no transfer ever starts) | None needed — the offer-then-fetch shape gates metadata before bytes (REQ-HAZARD-UPDATE-ROLLBACK) | `tests/propagate.rs::rollback_offer_is_rejected_before_any_fetch` |\n   │...\n 43│| 23 | **Psyche replies with no inbound sender to answer** (alarm-fired turn emits a reply) | That one intent drops with a typed outcome (fail-closed — never broadcast, never guessed) | Every perch (zero deliveries); the notify leg (independent) | The Psyche's next answered turn replies normally | `psyrelay.rs::reply_without_target_is_dropped` |\n*44│| 24 | **Live-Psyche driver loses stdout** (the 7.3 null-stdout shape: detached/`Stdio::null()` regression or a turn that produced nothing) | That turn fails loudly (`TurnError::EmptyOutput`); nothing relays | The relay boundary (no silent success — indistinguishable-from-bug shapes are errors); the spool | Fix the driver / retry the turn | `psyrelay.rs::null_stdout_driver_fails_the_guard`; `spt-live turn.rs::empty_stdout_is_an_error` |\n*45│| 25 | **Runaway Psyche dumps an oversize body** (> 64 KiB in one intent) | That intent drops with a typed outcome | Delivery substrate (never asked to carry it); other intents in the same turn relay normally | None needed (legitimate long bodies chunk at delivery — T3 `EVENT-PART`) | `psyrelay.rs::empty_and_oversize_bodies_drop_typed` |\n 46│| 26 | **Notif feed replayed whole** (re-presented rows after loss/restart — the push-feed's normal recovery) | None — the semilattice join no-ops (`Unchanged` per row, nothing written) | The spool (no duplicates, no state regression) | None needed: replay IS the recovery protocol (full-row re-presentation, no delta bookkeeping) | `tests/notifsync.rs::notif_spools_converge_over_the_wire_and_dismiss_replicates` (replay leg); `notifsync.rs::feed_converges_two_stores_and_dismiss_replicates` |\n 47│| 27 | **Concurrent dismiss + surface on two nodes** (the same notif dismissed on A while B marks it seen/surfaced) | None — the writes commute through the join (OR/union/max); both survive the bidirectional exchange | Both stores converge to identical rows; the dismiss latch (a stale undismissed copy can never un-dismiss) | None needed (no conflict exists to surface — every field is monotone) | `notifsync.rs::concurrent_dismiss_and_surface_commute_across_the_feed`; `spt-store notif.rs::merge_is_idem...\n 48│| 28 | **Untrusted origin injects notif records** (unpaired peer, or a `home`-trusted peer spoofing into `work`) | None — dropped fail-closed at the apply gate, zero rows written; the gate's subject is the handshake-proven stream-table origin, never payload bytes (KH 7.5) | The spool (nothing materialized); every subnet the origin isn't trusted in (trust is per-subnet) | None needed; pair the node to entitle it | `tests/notifsync.rs` (gate-negative leg); `notifsync.rs::untrusted_and_cross_subnet_origin...\n   │...\n 54│|---|---|---|---|---|\n*55│| P1 | **Iroh endpoint task hangs** (not crash — a wedged accept loop) | New inbound conns stall; existing conns + PTY hosting unaffected | PTY side fully isolated (separate threads/runtime) | D9 chaos pass |\n*56│| P2 | **mDNS discovery dies** | LAN dial-by-id fails; direct-addr + relay dials unaffected | Endpoint, conns, PTY | D9 (needs real LAN) |\n 57│| P3 | **Relay unreachable** (n0 outage / air-gap) | WAN dials needing relay fail; LAN/direct unaffected | Everything local | D9 two-host |\n*58│| P4 | **PTY broker drain thread dies** | That session's output stalls | Other sessions, net side | with terminal-wrapper hardening |\n 59│| P5 | **Manifest invocation hangs** (adapter subprocess) | That endpoint's lifecycle op | Daemon scheduler (KH 5.3 timeouts + 7.4 non-blocking) | when daemon hosts N agent loops |\n 60│| P6 | **Registry feed flood** (malicious/buggy peer spams updates) | Bounded by ring caps + lease; CPU cost unmeasured | — | D9 + REQ-SEC-1 outer gate |\n 61│| P7 | **Closed-watcher starvation** (net runtime saturated) | Presence lags behind reality | Conn table eventually consistent | D9 chaos pass |\n## FLAKE-LEDGER.md\n  9│|---|------|-------------|-----------|--------|\n*10│| 1 | `spt-daemon brain_restart_survives_gaplessly` | 1× hosted Windows (2026-06-03, hung) | ConPTY stall under hosted-runner load | job `timeout-minutes` bounds it; not seen self-hosted |\n*11│| 2 | `spt-term digest_parses_a_real_pty_stream` | 3× gravity (2026-06-03; 2026-06-04 runs 26957386850, 26966738231) | sprint-collapse assert (`digest.rs`) — PTY input echo and the child's write are **concurrent writers** into the master-read stream; back-to-back `send_line`s let a later line's echo interleave mid-`Write(…)`, breaking the tool regex for that turn | HARDENED (2026-06-04): sends serialized — each line pumped back (adaptive echo-copy count, 2s/line bound) before the next, so no concu...\n 12│| 3 | `spt-daemon sync.rs torn_pull` + `concurrent_writes` | 1× (2026-06-03) | `wait_for_stream` deadline too tight under load | HARDENED: `wait_for_stream_except` 2s→10s; no recurrence |\n 13│| 4 | `spt-live context::write_context_suppresses_stale_llm` | 1× gravity (2026-06-04 run 26967808321) | test calls `write_context` (reads SPT_HOME node identity + epoch stamp) WITHOUT the `with_home` lock — a parallel `with_home` test's env swap + temp-dir teardown races the stamp's epoch write into a deleted home (`NotFound`) | HARDENED (2026-06-04): test wrapped in `with_home` — serializes on the home lock like every other SPT_HOME-touching test |\n 14│| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the cl...\n   │...\n 18│| 9 | `spt dummy_harness_e2e` (BOTH tests: `endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches` + `endpoint_run_attach_awaits_online_before_attaching`) | 1× kitsubito (2026-07-05, REMOTE-TRUTH Batch-2 re-gate @f2a799e; roles swap run-to-run) | **process-global `set_var` race in a multi-test e2e binary.** Both tests `std::env::set_var(\"SPT_HOME\", own_tempdir)` then do IN-PROC reads (`perch::spt_home`/`adapters_dir`, `registry::register`, psychebin `fs::copy` staging). One file = one test b...\n*19│| 10 | `spt-store registry::tests::concurrent_registration_never_locks` | 1× hfenduleam local gate (2026-07-05, REMOTE-TRUTH Batch-2 bless matrix @3f6f327, fresh worktree, `nextest --workspace --no-fail-fast`) | 16-concurrent-writer SQLite registration gate FAILED at 5.63s — the SQLITE_BUSY load-contention class ALREADY documented at `db.rs:60`: box saturated by the 1588-test full-parallel suite meant a writer couldn't schedule its retry inside the default 5s `registry_busy_timeout`. Isolated ×5: gre...\n 20│| 11 | `spt-daemon::resume resume_mode_brain_spawning_new_sessions_delivers_each` | 1× kitsubito (2026-07-05 run 28768663321 attempt 1, REMOTE-TRUTH [twohost] PR #52) | Phase-B heavy-class timing EOF — \"session exited before the expected output arrived\" (UnexpectedEof) at 62s under the serialized heavy pool. SAME FAMILY as this file's prior Linux-CI flake (7940318, D4-2b: sequential spawn/read hardening) — a spawned session's exit raced the expected output read on the slow shared box. File untouched...\n*21│| 12 | `spt-daemon::input_ack_deadlock input_flood_through_serve_attach_does_not_deadlock_broker` | 1× hfenduleam local gate (2026-07-06, REMOTE-TRUTH F-2 matrix @a21bc6b, `nextest --workspace --no-fail-fast`) | deadline-margin under full-suite load: the flood exchange's `recv_timeout(30s)` (input_ack_deadlock.rs:512) blew under the 1608-test parallel pool + 7 leaky daemons — FAIL at 34.5s vs ~32s natural runtime isolated (the margin is ~3s by design). Subsystem disjoint from the triggering commit (F-...\n 22│| 13 | `spt::bin cli::tests::probe_all_cap_batches_into_windows` | 1× hfenduleam local gate (2026-07-06, stack matrix @0f08fa9 leg-3 rerun) | fast unit (0.52s natural) FAILED at 1.7s under the 1614-test full-parallel pool — probe-batching window assert with a timing component starved under load; subsystem disjoint from the triggering stack (F-2 reporting / E-2 grid title / picker rider). Isolated ×5: green, 0.52–0.55s. Same night as #12 — the full-parallel local matrix under a busy box is the com...\n*23│| 14 | `spt::bin rc::tests::attach_viewport_reconnects_across_a_broker_bounce` | 1× hfenduleam local gate (2026-07-06, WORKER-TRUTH W-3 build @worker-truth, `nextest -p spt --bin spt` full-parallel 360-test run) | **TIMEOUT, not an assertion fail** — SLOW>60…>TERMINATING at the 240s cap under the 360-way `--bin spt` pool. A REAL-broker + broker-bounce reconnect UNIT test (rc.rs:2071) that escaped the HEAVY-class serialization: the Phase-A/B split + `heavy-broker-pty` test-group target integration te...\n## GATEWAY-LIVENESS-DISPATCH.md\n  8│- **`spt endpoint list --json`** → **Suspended** (renders the endpoint via `resource_projection` over the registry snapshot, cli.rs:2855 → `EndpointRowJson.status` = the registry row status).\n *9│- flynn confirmed mobile-gw IS advertised in all 3 subnet registry files (NOT a re-advertise gap — his earlier \"empty by_id\" was a walker error, retracted).\n 10│\n 11│The adapter's suspend-honor poll reads `--json` (`parse_endpoint_status` over `endpoint list --json --show-all`) → sees Suspended → self-suspends the gateway, despite pid-alive + human-ONLINE.\n 12│\n## INJECT-SETTLE-REARM-GATE-VERDICT.md\n  5│## Scope\n *6│Post-0.29.0 field bug: PTY inject HEAD-TRUNCATION recurred on 0.29.0 — a checkpoint-wake payload injected right after `/clear` lost its head (perri screenshot `WindowsTerminal_6iSjya8pMt.png`). doyle root-cause + design ruling; todlando build.\n  7│\n  8│**Root:** `run_inject_worker` gated the W5-A Layer-1 settle-gate behind a worker-local ONE-SHOT (`settled_once`) on a FALSE premise (head-swallow race is startup-only). A mid-session `/clear` re-enters the harness raw-mode input reader → re-creates the pre-settle window, but the one-shot already fired at spawn → `settle_before_inject` skipped → head eaten again; echo-verify (Layer 2) default-OFF for that session → silent + unrecoverable.\n  9│\n*10│**Fix:** `settled_once` one-shot → `probe_unobservable` latch + pure `should_settle(attempt, probe_unobservable) = attempt>1 || !probe_unobservable`. Re-settle before EVERY delivery on an OBSERVABLE (echoing/interactive) PTY (bug-prone class, cheap); latch-skip the steady-state settle ONLY where the probe is UNOBSERVABLE (non-echoing ConPTY — no reattach race, full-deadline cost). The settle's own bool return discriminates the class, latched on attempt 1; a re-drive always settles. Mints REQ-HAZARD-I...\n 11│\n 12│## Diff review — FAITHFUL\n*13│Matches the design ruling exactly: latch semantics, pure gate, first-attempt-only latch update, re-drive-always-settles. KH 7.37 + traceable REQ minted with all three stages (`doc`/`impl`/`unit`) tagged on real evidence. RED-first unit `should_settle_rearms_on_observable_pty` present and correct. No int edit — safety-checked: `inject_control_wedge` mocks (`findstr`/`cat`) never answer the DSR probe → settle times out → `probe_unobservable` latches true after delivery 1 → identical to the old one-...\n 14│\n 15│## Both legs GREEN\n*16│- **Windows (ConPTY, isolated worktree + fresh target):** clippy `--workspace --all-targets -D warnings` = 0; `traceable-reqs check --json` = 0; nextest `--workspace` = **1707/1707** (9 leaky, 1 skip), exit 0. `should_settle_rearms_on_observable_pty` PASS.\n*17│- **Linux (kitsubito/forkpty, bundle-deploy @ddf7f8e):** clippy = 0; nextest `--workspace` = **1695/1695** (2 leaky, 1 skip), exit 0. `should_settle_rearms_on_observable_pty` PASS.\n 18│\n 19│## GATE-HOLD investigated → CLEARED (env contamination, not code)\n 20│First Windows full-suite run REDed on `inject_control_wedge::wan_arrival_to_idle_spt_hosted_injects_with_no_hook_poll` (`pending_spool_rows: left=1 right=0`, `INJECT_HEAD_LOSS_SPOOL` fired). Discriminated:\n 21│- Deterministic 5/5 isolated FAIL → not a flake.\n*22│- Root: `SPT_INJECT_VERIFY_ECHO=1` was exported in the gater's interactive live-agent shell (the live box arms Layer-2 echo-verify), inherited by the gate script → `echo_capable=true` → on Windows ConPTY the choreo child puts a headless line on the ring → Layer-2 false-positive head-loss → respool → `pending=1`.\n 23│- Proven env-driven: same test PASSES with `SPT_INJECT_VERIFY_ECHO` unset (deterministic both ways). Linux passed because its ssh env is clean.\n*24│- **CI-safe:** the var is **Process-scope only** (User/Machine scope empty) — the GitHub Actions runner service (hfenduleam, separate process tree) does NOT inherit it → PR CI runs clean. The clean-env full re-run (1707/1707) = CI's actual condition.\n 25│- The fix touches only Layer-1 settle; Layer-2 echo-verify is untouched W5-A code. The failure is orthogonal to the change.\n 26│\n 27│## Follow-up SEED (non-blocking; filed to todlando)\n## KNOWN-HAZARDS.md\n  70│### 2.5 Daemon-hosted endpoints have no dedicated liveness PID\n *71│- **Failure:** the sister evaluates Psyche/perch liveness via a dedicated process PID — the wrapper's own pid in `info.json`, checked with `is_process_alive`. Under ADR-0004 the Psyche (and any spt-hosted Self) is a **loop inside the daemon**, not a separate process: it holds no dedicated pid, and its `claude`/summarizer subprocess is ephemeral (spawned per pulse/commune, then exits). If a daemon-hosted perch's `info.json` carries the **daemon's** pid, then *every* hosted endpoint shares one pid, and `...\n  72│- **Invariant:** for **daemon-hosted** perches (Psyche; spt-hosted Self), liveness is the **daemon's authoritative in-memory endpoint table + a `status` field** on `info.json` (`online|offline|…`), **never** `is_process_alive(info.pid)`. `info.pid` for a daemon-hosted perch is at most a *hosted-by-daemon* marker (the daemon pid), not a liveness signal; registry stale-clean for these rows keys on the daemon's endpoint table, not per-row pid. This reuses the pattern already specified for **Shells** (`inf...\n  73│- **spt-core mapping:** the **M1/M2a interim** model keeps the Psyche/listener a real per-process owner (the `api listen` process), so its per-pid liveness (`deliver::is_online` → `info.read_pid` → `proc::is_process_alive`; `registry::clean_stale_entries`) is correct *interim*. **M3 daemon consolidation replaces it** with daemon-authoritative liveness for hosted perches. Keep the liveness check behind one resolver (mirrors `resolve_address` stale-clean) so the M3 swap is localized — do **not** let ...\n  74│- **Sister cite:** `src/live/wrapper/orphan.rs` (wrapper-pid liveness); `src/common/list_filter.rs:168-175` (pid-classify); spt-core `crates/spt-store/src/{proc.rs,registry.rs}` + `crates/spt-msg/src/deliver.rs::is_online`.\n    │...\n  80│### 3.1 Ephemeral perch cleanup on every `ring` exit path\n *81│- **Failure:** `ring` creates an ephemeral perch; early-exit paths (no-perch, empty-msg, timeout) skip cleanup → stale dirs accumulate.\n  82│- **Invariant:** every code path that creates an ephemeral perch cleans it before exit; exception: if the caller already had an active perch, do not treat as ephemeral and do not clean up.\n  83│- **spt-core mapping:** `ring` semantics carry; the daemon owns ephemeral-perch lifecycle, so a single guaranteed-cleanup (drop guard / RAII) is achievable in-process.\n  84│- **Sister cite:** `src/owl/ring.rs:58-294`.\n    │...\n 199│\n*200│### 5.5 ConPTY withholds output until DSR is answered  `[REQ-HAZARD-CONPTY-DSR]`\n*201│- **Failure:** a broker reading a ConPTY master sees only the 4-byte startup query `ESC [ 6 n` and then nothing — the child looks hung/silent but is producing output normally. ConPTY blocks all child stdout until the terminal answers the cursor-position query.\n*202│- **Invariant:** every ConPTY reader auto-answers DSR (`ESC [ 6 n` → write `ESC [ 1;1 R`, or a real cursor position) on the PTY writer. Secondary: a ConPTY master does not EOF while the writer is held, so read loops drain on a thread and never gate exit on a blocking `read()`.\n*203│- **spt-core mapping:** `spt-term` broker PTY reader (ADR-0004). Brand-new to spt-core — not in the sister project (it never hosted ConPTY directly).\n 204│- **Source:** Spike #1 (`docs/spikes/SPIKE-01-broker-handoff.md`); reproduced with both a Rust child and `cmd.exe`.\n 205│\n 206│<!-- [doc->REQ-HAZARD-DETACHED-PIPE-INHERIT] -->\n    │...\n 249│\n*250│<!-- [doc->REQ-HAZARD-WIN-PTY-PROGRAM-RESOLVE] -->\n*251│### 5.12 Native-PTY spawn of a bare program runs the wrong (non-PE) file on Windows  `[REQ-HAZARD-WIN-PTY-PROGRAM-RESOLVE]`\n*252│- **Failure:** `portable-pty`'s ConPTY spawn resolves a bare program name with a `which` that takes the FIRST `PATH` match. A node/npm CLI installs as BOTH an extensionless shebang shim (`ccs`, for Git Bash) and a Windows launcher (`ccs.cmd`) in the same dir; portable-pty picks the extensionless `ccs`, and `CreateProcessW` then tries to execute that non-PE file and fails with **os error 193** (\"%1 is not a valid Win32 application\"). Live failure: `spt endpoint run claude-spt:ccs` → `CreateProcessW C:\\n...\n*253│- **Invariant:** spt-term resolves the program ITSELF before handing it to `CommandBuilder`, bypassing portable-pty's `which`. A bare name is searched over `PATH` × `PATHEXT` (whose default order already prefers `.EXE`/`.COM` over `.BAT`/`.CMD`), then an extensionless fallback. A non-PE target is wrapped in its interpreter: `.cmd`/`.bat` → `cmd.exe /d /c <path>`, `.ps1` → `powershell -NoProfile -File <path>` (the wrap args precede the caller's args); a real executable spawns directly; an unresolvabl...\n*254│- **spt-core mapping:** `spt_term::winprog::{resolve_for_pty, resolve_in}` (the pure PATHEXT-precedence kernel + the Windows env wiring), wired into `spt_term::pty::PtySession::spawn_program_in`. Unit: `resolve_in` precedence (`.cmd`-over-shim, `.exe`-direct, explicit-extension, path-order, passthrough) [`winprog.rs`].\n 255│- **Source:** field diagnosis 2026-06-16 (operator dogfood, `claude-spt:ccs` bringup) — doyle.\n 256│\n 257│<!-- [doc->REQ-HAZARD-PERCH-RECORD-POWER-LOSS] -->\n    │...\n 300│\n*301│### 6.3 Cascade-wipe guard: never delete a parent hosting non-empty children\n 302│- **Failure:** `doctor --fix` deletes a top-level perch that still hosts in-flight nested Worker/Psyche perches.\n*303│- **Invariant:** before hard-delete, check for non-empty nested children; if present, soft-clean only and surface the path.\n 304│- **spt-core mapping:** any destructive maintenance command must check for live child instances first.\n 305│- **Sister cite:** CHANGELOG v1.11.20 Phase 35.1.\n 306│\n    │...\n 325│### 6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`\n*326│- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old c...\n*327│- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/0...\n 328│- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.\n 329│- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.\n 330│<!-- [doc->REQ-HAZARD-BROKER-PROCESS-ISOLATION] -->\n    │...\n 346│- **Invariant:** the daemon-hosted multi-session event loop must not consume a session's OUTPUT inside another session's blocking wait. The **live-agent adapter milestone must restructure the brain event pump** so command/`spawn` is non-blocking (a single demux loop owns `read_event` and routes every frame to its session's consumer), OR a blocking wait must re-queue/route the frames it reads for other sessions rather than dropping them.\n*347│- **spt-core mapping:** **unreachable today** — the supervised daemon brain hosts no PTY sessions and spawns none; a single-session seat (legacy, empty map) has no \"other session\" to starve. Surfaces the moment daemon-hosted sessions land (the live-agent adapter), which must rebuild the blocking `read_event` loop regardless (N interactive sessions cannot share one blocking reader). Recorded so that redesign inherits the constraint rather than rediscovering it. No machinery now (would be untested dead c...\n 348│- **Origin:** surfaced by the D4-2b resume-harness CI flake root-cause (agents `todlando` + `doyle`, 2026-06-10); sibling of `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]` 6.7.\n 349│\n 350│### 6.10 Phase-significant loop timing must be a durable absolute-deadline grid, not phase-relative sleep `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`\n    │...\n 375│### 7.2 Idempotent delivery across brain restart  `[REQ-HAZARD-RESTART-IDEMPOTENT]`\n*376│- **Failure:** broker queues, spool rows, PTY injection, remote streams, and file transfers cross the broker↔brain boundary; a brain restart duplicates or drops a side effect.\n*377│- **Invariant:** every side effect crossing the boundary carries a durable ID + replay rule; replay after restart is exactly-once / idempotent. Crash the brain before/after spool write, before/after PTY write, mid-transfer, mid-registry update — no dup, no drop.\n 378│- **spt-core mapping:** broker↔brain IPC (ADR-0004), self-update handoff.\n 379│- **Source:** codex Stage A #14.\n 380│\n    │...\n 403│- **Failure:** the peer pump is a SINGLE thread driving every leg (registry/notif/sync/update) against every peer over ONE brain-IPC client. Its reply reads (`net_open_stream`, `net_stream_send`, `net_dial`, and the sync/update pull `read_event` loops) were `loop { read_event() }` with no deadline. When a peer's QUIC path black-holes, the broker's stream-open/send awaits the dead peer and never sends the reply, so the brain's `read_frame` blocks FOREVER and the pump freezes mid-round. The heartbeat (loop...\n*404│- **Invariant:** in PUMP mode the brain carrier is **SPLIT at construction** — a dedicated `pump-ipc-reader` thread does blocking `read_frame` on the `RecvHalf` and forwards each framed result down a channel; the main thread writes on the `SendHalf` and reads with `Receiver::recv_timeout`. Every IPC reply read is bounded by a per-call **total-wait** deadline (`PUMP_PEER_IO_TIMEOUT` = 30s, > any legitimate round-trip, < the 60s QUIC idle; re-armed on stream progress for the streaming pull legs so a heal...\n 405│- **spt-core mapping:** `Brain::cold_start_pump` (splits the carrier + arms the deadline) / `BrainConn::Split` (the `SendHalf` + reader-thread channel) / `call_deadline` / `read_event_until` / `read_frame_until` (the `recv_timeout` dispatch), `pump::run_peer_pump` (connects in pump mode) + `pump::peer_outcome` (the tier-split), the `request_sync`/`request_update` pull loops (deadline re-armed on progress). The broker-side half — the broker must never make a brain wait unbounded on a QUIC op (bound the ...\n 406│- **Source:** field diagnosis 2026-06-11 (the 2.2h hfenduleam wedge); doyle ruling A-now / B-deferred. The stall warning (M8 decision 23) was the band-aid; this is the fix. The B-half landed v0.8.3 (§7.8) after the 2026-06-16 recurrence.\n 407│\n    │...\n 409│<!-- [doc->REQ-HAZARD-VIEWER-ISOLATION] -->\n*410│- **Failure:** the W2.5 controller/viewer model lets ANY number of read-only `--view` attachers ride one session's broker `OutputLog`. The single drain thread fans each output chunk to every attacher. If a viewer's socket is fanned out with a **blocking** write under the log lock (the controller's authoritative path), one wedged viewer (a slow terminal, a black-holed WAN peer, a hostile non-reader) stalls the drain — freezing the controller's stream and backing up the PTY child. A single watcher must n...\n 411│- **Invariant:** the drain writes the **controller** on the authoritative blocking bounded path (it alone advances `delivered_through`), but each **viewer** gets an **isolated bounded SPSC queue + a dedicated writer thread**; the drain `try_send`s under the log lock and **evicts** any viewer whose queue is `Full` (fell behind the live stream) or `Disconnected` (its writer died on a dead socket) — the drain thread **never touches a viewer socket**, so no viewer write can backpressure it. A **soft cap** ...\n 412│- **spt-core mapping:** `OutputLog::append` (controller blocking + `viewer_send_evicts` `try_send` fan-out), `OutputLog::add_viewer` (bounded `sync_channel(VIEWER_CHANNEL_DEPTH)` + `viewer_writer` thread), `MAX_VIEWERS` soft cap, `ViewerSink`. Unit: `viewer_overflow_or_disconnect_evicts_never_blocks`. Int: `wedged_viewer_does_not_stall_controller` (a non-reading viewer is evicted while the controller keeps receiving past a 200KB burst).\n 413│- **Source:** M12 W2.5 controller/viewer model (doyle ruling 2026-06-14, Q1).\n## MANIFEST.md\n 129│<!-- [doc->REQ-PSYCHE-CONTEXT-FILE-INDIRECTION] -->\n*130│**Psyche-download — `{psyche_context_file}` (file-always, replaces `{psyche_context}`).** The composed Psyche mind rides a **file**, never the command argv: before each turn spt-core writes the mind into the nested psyche perch dir and fills a single **`{psyche_context_file}` = that path** (argv-cap-immune — a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / re...\n 131│\n 132│<!-- [doc->REQ-PSYCHE-NESTED-RESOLUTION] -->\n 133│**`{subnet}` — the endpoint's home-subnet label.** A single `{subnet}` key fills to this endpoint's home-subnet label (`local` when unhomed), supplied whenever it is known so a psyche turn need never resolve a `--subnet` it cannot know; when no subnet is known the key is left unfilled so a referencing template fails **loud** (the `{node}` precedent). There is deliberately **no `{home}` key** — the endpoint home subnet is one concept, and `home:` is already the subnet-name qualifier position, so a `{h...\n    │...\n 135│<!-- [doc->REQ-PSYCHE-SID-CUSTODY] -->\n*136│**Reseed — reserved exit code 95.** When the Psyche's own session is lost or invalid (first host, store wipe, harness-side expiry) the adapter signals it with the **reserved exit code 95**; spt-core clears the stored custody sid and drives the next turn as a fresh preload (a non-empty `{psyche_context_file}`), emitting a loud `PSYCHE_RESEED:<id>` marker so a custody-loss loop is visible — the failure budget above bounds it.\n 137│\n 138│<!-- [doc->REQ-SESSION-RESUME-TEMPLATE] -->\n 139│**`[session.resume]` — the agent's own-session NATIVE resume.** `self` is the\n    │...\n 151│harness resolves a transcript by `session_id` **+ cwd**, so spt-core also lands\n*152│the resumed PTY in the session's recorded **project cwd** — the per-session\n 153│ledger row's `cwd` → else the perch `info.cwd` → else the current dir (so a\n 154│single-project endpoint and pre-migration ledger rows still resume correctly).\n 155│\n    │...\n 178│|---|---|---|\n*179│| `{node}` | this node's **advertised label** — the label store value (OS-hostname default, re-checked at daemon startup), OS-hostname fallback when no store label is known | **SINGLE-TOKEN fill only** — tokenize-then-fill cannot produce a space-carrying argv element, so a composite display name like `<id> @ <node>` stays adapter-shim territory (`{node}` complements the shim for tokenizable args, e.g. `--remote-control {id}--{node}`, it does not replace it). **Caveat:** the daemon-side lifecycle reso...\n 180│\n 181│<!-- [doc->REQ-MANIFEST-NODE-KEY] -->\n 182│```toml\n    │...\n 302│--sample <real-log>` runs it and prints the parsed records, the rendered digest,\n*303│and **every dropped line with its reason** — no silent empty.\n 304│\n 305│<!-- [doc->REQ-ADAPTER-PROOF-DIR-OVERRIDE] -->\n 306│To proof a **DEV build that is not registered** (a freshly built binary beside a\n    │...\n 322│<!-- [doc->REQ-MANIFEST-SUBST] -->\n*323│**Lazy substitution inside `[strings]` values (since v0.16.0).** A `[strings]` value (inline literal or the read-back contents of a pointer file) may contain the **adapter-static** placeholders `{adapter_dir}` / `{adapter_name}`; `get-string` substitutes them at read time and returns the resolved string. This is scoped to **those two keys only** — `get-string` carries no session context, so session-scoped keys (`{id}`, `{session_id}`, …) are **not** available here and an attempt to use one is left un...\n 324│\n 325│```toml\n 326│[strings]\n    │...\n 333│### `[inject]` — inject-input methods\n*334│Per activity-state, any combination: `pty`, `hook`, `relay`, `http`.\n 335│```toml\n 336│[inject]\n 337│activity = [\"hook\"]                # non-disruptive during work\n*338│idle     = [\"pty\", \"hook\"]\n 339│```\n 340│\n 341│<!-- [doc->REQ-MSG-IDLE-TRANSLATION-BINARY] -->\n    │...\n 346│feed and reads back keystroke-commands, which spt-core applies to the broker-held\n*347│PTY **atomically** — controller input is buffered during the emitted sequence and\n 348│flushed after, so injection coexists with a live `spt rc` controller (spt-core owns\n*349│every PTY write). Idle-only; busy/mid-turn delivery stays adapter hook-injection.\n 350│A **table** carrying a `command` (or the deprecated bare `path`) scalar (not a bare top-level scalar — a table can't be\n 351│silently absorbed by a preceding section, and it stays extensible).\n 352│```toml\n    │...\n 361│- Whichever form is used, the resolved program is spawned at the initial spt-hosted bringup **and** at a live-update respawn. (A declared binary that fails to spawn is logged `TRANSLATION_SPAWN_FAILED` on the daemon's stderr; before diagnosing idle-delivery behavior, confirm the binary actually spawned.) <!-- [doc->REQ-INSTALL-11] -->\n*362│- **stdin** (spt-core → binary, one JSON object per line): `{\"type\":\"init\",\"endpoint_id\":…,\"node\":…}` first · `{\"type\":\"event\",\"envelope\":\"<EVENT…>\"}` per inbound message (the ADR-0020 envelope) · `{\"type\":\"input\"}` a **content-free** ping each time the operator types (so the binary can track user-idle; the PTY input content is **never** duplicated to the binary).\n 363│- **stdout** (binary → spt-core, one per line): `{\"key\":\"ctrl+s\"}` · `{\"delay_ms\":50}` · `{\"text\":\"<payload>\"}` · `{\"key\":\"enter\"}` · **`{\"commit\":true}`**, … (extensible vocabulary).\n*364│- **`{\"commit\":true}` is the mandatory sequence TERMINATOR, required for EVERY `{\"type\":\"event\"}`.** While a sequence is in flight spt-core buffers the live `spt rc` controller's keystrokes (the *inject floor*) and applies the emitted commands atomically; the trailing `{\"commit\":true}` releases the floor and flushes the buffered controller input *after* the sequence. The submit keystroke is NOT the terminator (a choreography may keep typing after `{\"key\":\"enter\"}`). **An empty response is a protocol viol...\n 365│- **Commit deadline + miss tolerance (F029 C-1, ADR-0022 amendment).** If no `{\"commit\":true}` arrives within the **commit deadline (5 s, `INJECT_COMMIT_DEADLINE`)**, spt-core flushes the buffered operator input (never stranded) and re-spools that message once — but a SINGLE miss does NOT fault a healthy binary (it is preserved; the next event delivers through it). Only **3 consecutive** missed commits, or a real binary death, faults the binary — and a real fault gets a **bounded eager respawn** (a h...\n 366│- **Known keys today:** `command` (preferred) and the deprecated `path`. spt-core does **not** `deny_unknown_fields` here — a newer adapter declaring a future key against an older spt-core parses fine (the unknown key is ignored), so the lifecycle-binary contract degrades gracefully.\n 367│- A minimal binary just emits `{\"text\":payload}{\"key\":\"enter\"}{\"commit\":true}` with no choreography (spt-hosted idle delivery is translation-binary-only since v0.14.3 — no raw-inject fallback).\n    │...\n 385│<!-- [doc->REQ-TERM-5] The session digest is a PROJECTION of the endpoint's\n*386│session logs, never a PTY-byte parse (the superseded source mechanism). ADR-0019\n 387│gives it its OWN manifest seam — the `[digest]` extractor above — distinct from\n 388│`[history]` (which stays opaque + single-session, feeding the echo-commune\n 389│verbatim). The M9 \"no manifest seam / rides `[history]`\" stance is REVERSED: one\n    │...\n 404│- `role` — the closed set `input` | `agent` | `tool` (the source tag).\n*405│- `text` — the input / agent span (omitted/empty for a `tool` record).\n 406│- `tool` — `{name, arg}`, present **iff** `role == \"tool\"`. Consecutive `tool`\n 407│  records collapse into one sprint (unless `sprint_collapse = false`).\n 408│- `ts` — optional RFC3339-UTC ordering key; the two-origin merge interleaves\n    │...\n 424│therefore **MUST classify a delivered user-facing message as a turn-opening\n*425│`input`** record (equivalent to a direct PTY user-input) — not as `agent`/`tool`\n 426│output. If messaging-delivered turns are not opened as `input`, a messaging-driven\n 427│session collapses into a few giant turns and `--last <N>` / `seq` lose their\n 428│granularity. *What* becomes an `input` is the adapter's call; that it opens a turn\n    │...\n 502│[update.post]\n*503│command = \"{adapter_dir}/claude-spt post-update\"   # REQUIRED, non-empty; install-dir + adapter-static subst\n 504│self_verifies = false                              # optional; attestation-only metadata (default false)\n 505│```\n 506│\n*507│- **Runs unconditionally** — the post-step fires **even when the primary avenue was a no-op** (already up to date). Its own idempotent check (e.g. `claude plugin update`) decides whether anything changes. `command` is **required** and **non-empty** (an empty `command` is refused at registration). `self_verifies` is **attestation-only metadata** (defaults `false`) — parallel to `[update].self_verifies`; it records that the post-step verifies its own outcome but **gates nothing** (the post-step is alre...\n 508│- **Published stdin seam** — spt-core feeds the post-step **one JSON line** on stdin describing the just-resolved update:\n 509│  ```json\n 510│  {\"adapter_applied\": true, \"adapter_name\": \"claude-spt\", \"profile_name\": null,\n    │...\n 518│- **stdout decides the notice** (the post-step knows `adapter_applied` and owns the call):\n*519│  - **non-empty custom text** → printed verbatim, and it **supersedes** `[update].message` (a dynamic notice).\n 520│  - the **reserved sentinel `!!update-message!!`** alone → fires the static `[update].message` (house `!!x!!` style; a published seam — emit it verbatim and alone).\n*521│  - **empty** → **no notice**.\n 522│- **exit code is orthogonal to the notice**: `0` = ran OK, nonzero = failed (it does not by itself suppress or change the notice).\n 523│- **Notice precedence:** dynamic stdout text > sentinel / `[update].message` > nothing. With **no `[update.post]`**, behavior is unchanged from today: an applied update fires `[update].message`. If the **post-step fails** (nonzero exit / spawn error), spt-core emits a **loud warning** and **falls back** to the today behavior (`adapter_applied` → `[update].message`) — a plugin-sync failure never swallows the adapter notice.\n 524│- **Failure-isolated:** a committed `gh_release` pull is **never rolled back** if the post-step fails — the pull and the post-step are independent channels.\n    │...\n 550│pre_close = \"park-and-save\"        # optional instruction sent to the binary on link-break\n*551│close_timeout_ms = 3000            # graceful termination window before force-close\n 552│persistent = true                  # auto-online whenever the owner endpoint is online\n 553│wake_command = \"gamerobot-waker --link {link_token}\"  # long-running wake-watcher run WHILE offline; exit(wake-opcode) ⇒ revive\n 554│can_shutdown = false               # if true, the shell may fire `api owner-shutdown` to suspend its owner directly\n## MESH-RECOVERY-TRIAGE.md\n 80│5. **Migration preserves fallback.** The W2 rebuild touches invalid rows\n*81│   only; valid rows and the absent-file/corrupt-degrades-empty behavior are\n 82│   untouched (existing REQ-CONV-1 units must stay green unmodified where\n 83│   behavior is unchanged, amended where drop-on-fail semantics changed —\n 84│   list the amended ones explicitly in the PR).\n## MSG-IDENTITY-DISPATCH.md\n 43│- `REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE` [impl,unit,int] — never delete a drop whose slice\n*44│  wasn't durably committed (ingest.rs:156 empty-project_id gate vs :200 unconditional delete).\n 45│  Distinguish precedence-SUPPRESSED (safe delete) from un-committable-now (keep/preserve).\n 46│  Repro fixture banked (F-032-commune-2026-07-08T222721Z.md). Field-verify: perri.\n 47│\n## MUSL-W3-DISPATCH-RULING.md\n 13│## Scope — release-pipeline data, NOT dependency work\n*14│`/diagnose` 2026-07-08 (backlog #14) proved the tree builds+runs static musl with ZERO source changes (rustls not openssl; aws-lc-sys + bundled-sqlite clean under `musl-gcc`; openpty; static DNS/HTTPS). So W3 is CI-matrix + assemble + sign + update-set wiring. Sites:\n 15│\n 16│1. **`release.yml` — musl matrix entry** (build on **kitsubito**, the proven musl toolchain box):\n 17│   - install in-job: `musl-tools` + `cmake` + `rustup target add x86_64-unknown-linux-musl`\n## NEXT-MILESTONE-BUG-TRIAGE.md\n  11│- **C — CLI / picker UX:** #1, #5, #11, #13\n *12│- **D — PTY / terminal rendering:** #6, #7, #8, #12, #14\n  13│\n  14│---\n  15│\n    │...\n  71│\n *72│## Cluster D — PTY / terminal rendering\n  73│\n  74│### D-#6 — scrolling broken for CC (Claude Code TUI)  · STATUS: TRIAGE\n *75│Everything above the CC TUI content shows raw terminal history (scrollback corrupt). Proposal: broker uses win32 vterm? Acceptable that Ctrl+B breaks if non-SPT PTY features all work.\n *76│- Root cause: _TBD (broker PTY scrollback / alt-screen handling)_\n  77│- Fix approach: _TBD_\n  78│\n *79│### D-#7 — PTY clutter as the TUI animates/scrolls  · STATUS: TRIAGE\n  80│Residual characters left in whitespace as the TUI animates (image: stray chars). Possibly related to the new top-right ID marker injection.\n  81│- Root cause: _TBD_\n  82│- Fix approach: _TBD_\n    │...\n 119│\n*120│## Cluster D — PTY/terminal rendering (DIAGNOSED)\n 121│\n 122│**ID-marker spine (#7/#8/#14):** it's `StatusRow`/REQ-RC-IDENTITY (v0.16.0 W6), CLIENT-side in rc.rs:198-307 (NOT broker). Paints `{subnet}:{id}@{node}` right-aligned cyan on real terminal row 1 via DECSTBM scroll-region `ESC[2;{rows}r` + DECSC `\\x1b7`/home/`\\x1b[2K`/right-align/cyan/DECRC `\\x1b8` — ABSOLUTE one-shot. Repainted only on: attach (1297-1302), ReassertScanner alt-screen/DECSTBM-reset hit (1413-1419), polled size-change (1329-1349). NOT on ordinary scroll/redraw.\n 123│\n*124│**D-#6 scrollback corrupt** · conf H · effort L · cross-platform. ROOT: broker is raw-byte pump, NO terminal/grid model — OutputLog.ring VecDeque<(seq,Vec<u8>)> (broker.rs:213-216), on attach replays raw bytes from from_seq; cold rc → net_from_seq=0 (attach.rs:198-213)+cursor=0 (rc.rs:1313) → ENTIRE ring dumped into fresh terminal. For alt-screen TUI (CC) history has interleaved ?1049h/l + scroll-region + abs-cursor → flips alt on/off mid-replay → TUI frames into main buffer = corrupt scroll...\n 125│\n*126│**D-#7 residual chars on animate** · conf M · effort M · coupled marker · cross-platform. ROOT: (1) raw passthrough can't clear cells / reconcile ConPTY repaint-dup (stream.rs:11-15); (2) StatusRow injection (rc.rs:1404-1420) splices DECSTBM+DECSC/2K/SGR/DECRC into harness in-flight drawing — DECSC/DECRC unreliable on legacy conhost; scanner runs after writing chunk → banner spliced mid-sequence; reserved scroll-region fights CC's abs-cursor moves. FIX: folds into #6 grid; short-term disable mark...\n 127│\n 128│**D-#8 resize artifacts + marker vanishes** · conf H(marker)/M(artifacts) · effort S-disable/L-right · cross-platform. ROOT: marker is static absolutely-positioned one-shot, repainted only on detected size-change/reassert; resize reflow pushes row-1 banner into scrollback + re-emits dup cells; written-to-buffer not live-overlay → \"only visible if you scroll up\". NOT an axis-swap (size destructure correct). FIX: sticky overlay (needs #6 grid); until then disable.\n 129│\n    │...\n 133│\n*134│**D sequencing:** (1) flag the ID-marker OFF now → cheaply fixes regression-half of #7/#8 + all #14. (2) #12 independent + cheap (VT-out enable) → do regardless. (3) #6 server-side grid = foundational, real fix for #6 + prereq for redoing marker as sticky overlay (#8/#14) + killing residual artifacts (#7). Key files: rc.rs (StatusRow/RawGuard/pump), broker.rs (ring+replay), attach.rs (from_seq=0), spt-term/{lib,stream,pty}.rs.\n 135│\n 136│## Cluster B — cross-node messaging/routing (DIAGNOSED)\n 137│\n    │...\n 149│\n*150│**A-#3 controlled-state not gossiped (driven_by is remote-only BY DESIGN)** · conf H · effort M. ROOT: W5 wiring is correct end-to-end, but the SOURCE is structurally None for a host-local controller. advertise_local reads `info.json.driven_by` (registryhost.rs:437) ✓; picker reads controller_node→Controlled (data.rs:289/model.rs:312) ✓; BUT `driven_by` stamped by broker from `controller_by().by` (broker.rs:630-636/384-386), and `by` = Some(origin_node) ONLY for a REMOTE WAN attach (attach.rs:337...\n 151│\n 152│**A-#15 dormant on lone detached instance — FAITHFUL to CONTEXT, display problem** · conf H · effort S-display/M-model. ROOT: resting.rs:123 `(Active,Detach)=>Dormant` exactly implements CONTEXT.md:283/600 (\"active→dormant: the driver detaches\"); active REQUIRES a driver (CONTEXT:599). NOT an impl defect — it's CONTEXT's internal tension (281 calls active/dormant the \"routing differentiator\" yet 283/600 make Detach→Dormant even for a lone instance). Naive fixes all wrong: Detach→Active breaks...\n 153│\n    │...\n 162│- **WAN cluster (ordered):** B-#9/#10(b) reply-leg FIRST (makes loss visible) → B-#4 cross-node rc (reuse resolve→dial→round-trip) → B-#9/#10(a) address-refresh-on-IP-change (L).\n*163│- **PTY foundational (L):** D-#6 server-side VT grid → unlocks proper sticky marker redo (#8/#14) + kills residual artifacts (#7).\n 164│- **Design-gate first:** A-#15 model-change (if wanted) needs CONTEXT.md amendment; C-#1 decide if install-time runs [update.post].\n 165│\n 166│---\n    │...\n 173│`spt rc eel-a` → `RC_FAIL:eel-a: attach request: brain IPC read deadline elapsed`. Worked shortly after creation. **LIVE EVIDENCE (doyle, this box):** endpoint list/daemon show eel-a `ready=true alive=true` (alive is BIND-gated — set at api bind, does NOT reflect live brain health), yet the attach IPC times out → the brain isn't servicing IPC (wedged/dead) while the perch still reads alive. daemon pid 19180. **STRONG LEAD:** the v0.18.0 `spt update apply` daemon/brain RESPAWN (done on this box earl...\n*174│- **Root cause (agent E, conf M-mechanism-H): broker-resident shared-state corruption from the self-update brain-RESPAWN — NOT the brain itself.** The only deadline-bounded call in request_attach is `net_open_stream` (rc.rs:1086, awaits NetStreamOpened under ~10s); error minted brain.rs:1442 = broker's per-conn serve thread sent NO frame for KIND_NET_STREAM_OPEN. DISCRIMINATOR: on the same rc conn, KIND_SESSIONS + KIND_NET_DIAL_LOOPBACK already SUCCEEDED (broker pid 19180 alive, thread reached attach) ...\n 175│\n 176│### G-#17 — no digest for eel-a  · STATUS: SHIPPED CLOSED — W6 env-read @9305d19 (REQ-DIGEST-PROFILE-ENV) + W6b fetcher-strategy @b9e5ce9 (REQ-DIGEST-FETCHER-STRATEGY)\n 177│> **W6** shipped the harness-agnostic `[env] direction=\"read\"` seam (capture-at-bind → info.json.read_env → digest fill_template keys → `value` fallback → `~` expansion → skip-diagnose). **W6b** closed the `{project}` design-gate per doyle's CONTEXT-symmetry ruling: `[digest]` gained a `fetcher` strategy mirroring `[history]`'s — the ADAPTER's extractor locates+reads+emits (no spt-core pre-read, no `{project}` slug), fed only harness-neutral `{session_id}`+`{cwd}`(=info.json.cwd)+captured rea...\n 178│`spt endpoint digest eel-a` → `NO_DIGEST:eel-a has no activity buffer (no session-log source / no records yet?)`. **LIVE EVIDENCE:** eel-a has ZERO entries in daemon-effects.log → no records produced. Operator hypothesis: manifest setup vs code, perri-side. Either (a) the claude-spt adapter's session-log source (manifest [digest]/session-log path) isn't wired for eel-a, or (b) eel-a's brain wedged (#16) before producing records. Disentangle adapter-manifest-wiring vs spt-core-digest-source vs downstr...\n*179│- **Root cause (agent G, conf H): ADAPTER-MANIFEST CONFIG (perri-side), NOT spt-core, NOT downstream of #16.** digest reads ONLY two sources, neither auto-populated by spt-core: (1) `[digest]` extractor (digest.rs:164-177 → spt_live::extract_digest) resolving `[digest].source` template (else `[history].locate_template`) over the harness's own transcript (CC `~/.claude/projects/{project}/{session_id}.jsonl`); (2) log-less `perch/digest.log` via explicit `api digest-entry`. NO_DIGEST = both empty + never...\n 180│\n 181│### G-#18 — `spt adapter update claude-spt` re-register fails  · STATUS: SHIPPED @9305d19 (W6, REQ-ADAPTER-UPDATE-INPLACE)\n 182│`claude-spt 0.9.0→0.9.2`: fetch OK, unsigned-trusting OK, LIVE daemon-coordinated apply, then `ADAPTER_UPDATE_FAIL:claude-spt: re-register: io: The system cannot find the file specified. (os error 2)`. The post-extract re-register step can't find a file — likely the extracted adapter root path or a manifest-referenced binary/install_dir. Class: adapter update ripple / install-dir resolve (cf. F-019 translation install-dir, REQ-HAZARD-DEFERRED-MANIFEST). perri-adjacent (claude-spt v0.9.2).\n*183│- **Root cause (agent G, conf H): SPT-CORE DEFECT (3 coupled), NOT perri packaging, NOT coupled to #16.** Chain: update writes to HARDCODED `_github/{safe}` dest (cli.rs:7674) ignoring the adapter's registered source_dir — claude-spt was plugin/sptc:setup-installed, NOT via `adapter add --release`, so `create_dir_all(dest)` makes a FRESH EMPTY dir. `adapter_has_live_endpoint` (cli.rs:7394/7407) scans on-disk perch info.json parent-prefix → eel-a alive → routes to `apply_release_via_daemon` (ADAPTER...\n 184│\n 185│## REOPENS (new evidence refutes/extends prior diagnosis)\n 186│\n    │...\n 199│\n*200│**Adapter-hosting clarification (operator):** claude-spt is a `kind=harness` adapter that supports BOTH spt-hosted endpoints (via `[session.self]` → `endpoint run`/`bind` → broker-hosted PTY) AND harness-hosted. **eel-a IS spt-hosted** (record + the fact it has broker/rc involvement proves it). The subagents conflated \"harness *adapter*\" with \"harness-*hosted* endpoint\" — wrong. This invalidates the empty-affected framing in #18.\n 201│\n 202│**#17 CORRECTED (agent G was WRONG — [digest] IS wired):** the installed claude-spt manifest DOES declare `[digest] extractor = \"claude-spt-digest --session {session_id} --in {source}\", source = \"~/.claude/projects\"`. eel-a's info.json HAS a stamped `session_id = d84df205-6f96-4379-ad7a-c5fa1067aa46`, adapter=claude-spt, spt-hosted. So NO_DIGEST is NOT a missing-manifest-config. VERIFIED real cause: **no CC transcript exists for the stamped session_id** — `find ~/.claude/projects -iname \"d84df205*\"` ...\n 203│\n*204│**#18 CORRECTED (agent G D1/D2 framing wrong; D3 CONFIRMED by evidence):** claude-spt record.toml → `source_dir = …\\_github\\SaberMage-spt-claude-code` (mode=pointer). The `[update]` repo = `SaberMage/claude-spt` → the update derived dest `_github\\SaberMage-claude-spt` (≠ the registered source_dir!) and `create_dir_all`'d it — it is now EMPTY (verified: `_github/SaberMage-claude-spt/` contains only `.`/`..`). So the update wrote/re-registered to the WRONG, empty dir → `register` reads `_github...\n 205│\n 206│**#12 REFINED (operator):** \"ring rolled past setup\" is NOT guaranteed — a low-activity session could still hold its setup in the ring and render fine. The SOLID, dispositive fact stands: rc and `endpoint run --attach` are the SAME client code (VT theory refuted). The garbling is a server-side REPLAY-CONTENT problem (no grid model) — exact trigger varies (ring rolled past setup on a busy session, OR a torn sequence at the ring's oldest retained chunk, OR alt-screen/scroll-region state not reconstruct...\n 207│\n    │...\n 212│## CORRECTION 2 (operator, 2026-06-30 — my #17 \"no transcript\" was WRONG)\n*213│- **#17 RE-CORRECTED:** eel-a runs the `claude-spt:ccs` profile. The transcript DOES exist — under `.ccs\\` not `.claude\\`: VERIFIED `~/.ccs/instances/bigscreen/projects/C--Users-decid-Documents-projects/d84df205-….jsonl`. Manifest (lines 24-30) is explicit: `[profiles.ccs]` deliberately has NO `[profiles.ccs.digest]` override because ccs relocates CC's transcript tree via a RUNTIME `CLAUDE_CONFIG_DIR` env (→ `~/.ccs/instances/<account>/.claude`) that isn't a static catalog path — resolution is me...\n 214│- **#18 CONFIRMED (repo rename is INTENTIONAL, not a perri bug):** perri+operator deliberately renamed the repo (spt-claude-code → claude-spt) between releases. So `adapter update` MUST update in place at the registered `source_dir` and tolerate a changed `[update].repo`/URL — never re-derive `_github/<safe>` from the repo name. Drop the \"perri inconsistency\" note; the fetch-from-new-repo worked, only the install-dir derivation is the spt-core defect.\n 215│- **LESSON (reinforced):** on adapter/profile questions, verify against ALL relevant on-disk trees INCLUDING profile-relocated paths (`.ccs\\` as well as `.claude\\`) before asserting absence. I asserted \"no transcript\" having searched only `~/.claude` — wrong twice on this bug.\n 216│\n    │...\n 218│- **#11:** picker remote-detail + status palette DID ship in v0.17.0 (W5) — CHANGELOG bullet \"endpoint picker shows remote endpoints in full detail\" + the lifecycle \"Reading the picker\" legend; it just didn't spell out \"colored squares.\" #11 here = bring the non-interactive `endpoint list` to that same palette (NOT yet shipped).\n*219│- **#14:** confirmed — switch the top-right ID badge OFF next release; save the concept for a future web SPT GUI. **NON-NEGOTIABLE: accurate PTY representation with zero artifacts** (raises the bar on #6/#7/#8 — a real fix, not just disabling the badge).\n 220│- **#6:** server-side grid approach confirmed correct.\n 221│- **#15:** display-fix path accepted.\n 222│\n## NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md\n  51│**REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE** (doc, impl, unit, int)\n *52│Root, rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): PTY viewer fan-out\n  53│and control mutations depend synchronously on a live, DRAINING brain — brain-subscriber\n  54│session-output writes ride UNDER the per-session log lock (broker.rs:19-20). Failed writes are\n  55│handled (cursor freeze + detach, :3486); BLOCKED writes are not. Suspended brain ⇒ within\n    │...\n 134│> idle-inject probe woke doyle's drain — parked, not truncated. **(b) live-SENT\n*135│> truncation/empty-body** = REQ-INJECT-MULTILINE-INTEGRITY (below) — a genuinely\n 136│> mangled payload on the typed leg, unrecoverable (no spool copy). Do NOT conflate:\n 137│> (a) is a scheduling/wake gap (bytes intact, late); (b) is byte corruption in transit.\n 138│> File-transport workaround (proposals/reports ride branch files, sends carry only the\n    │...\n 159│> (Field instances this milestone, all live-SENT, no spool copy, unrecoverable:\n*160│> todlando's W1 gate report HEAD-TRUNCATED at doyle; a flynn EVENT fully EMPTY.)\n 161│\n 162│Root: the idle-inject TYPED delivery leg eats HEAD bytes\n 163│nondeterministically — spool rows complete (1669B) vs ~322B received suffix; mid-turn poll\n    │...\n 168│spool copy — truncation there is unrecoverable, which raises the stakes.\n*169│Int: repeated large multi-line injects into a real PTY session arrive byte-complete (loop N\n 170│times — the race is timing-dependent, single-shot green is not proof).\n 171│\n 172│**REQ-IDLE-PARKED-DELIVERY** (impl, unit, int)\n## NEXT-MILESTONE-MUSL-TIER-TRIAGE.md\n  8│## Proven feasibility (doyle /diagnose 2026-07-08 — findings-backlog seed #14)\n *9│- **Builds + runs static musl with ZERO source changes** — `cargo build --release --target x86_64-unknown-linux-musl -p spt` EXIT 0 on kitsubito (`musl-tools` + `cmake` + `rustup target add` only); `file` = `static-pie linked`, `--version` runs. `aws-lc-sys` + bundled `rusqlite` compiled clean under `musl-gcc`; TLS = rustls (no OpenSSL). PTY = openpty (musl-provided).\n 10│- **DNS/HTTPS works statically** — a real `spt update fetch` on the static binary resolved DNS, reached GitHub, and downloaded the update-set + artifacts. The NSS/`getaddrinfo` static-musl fear is **refuted** (only NSS plugins break, not plain DNS). No reqwest/resolver change needed.\n 11│- **The ONLY gap is the release model** — that fetch ended `UPDATE_FETCH_REJECTED:NoArtifactForPlatform(\"unknown\")`: `current_platform()` returns `\"unknown\"` for `target_env=\"musl\"`, and no musl artifact is published. That is exactly what this milestone fixes.\n 12│\n## NEXT-MILESTONE-PICKER-POLISH-TRIAGE.md\n  37│rest) render bare `r.display` with no cwd affordance.\n *38│**Fix shape:** when `r.dir == run_cwd` (non-empty), label =\n  39│`format!(\"{} (CURRENT DIR)\", display)`. Pure fn, unit-complete (extend the\n  40│:1867-1889 test cluster).\n  41│\n    │...\n 245│     binary MUST answer every `{type:event}` with at least a bare\n*246│     `{\"commit\":true}`** (an empty response is a protocol violation the\n 247│     deadline treats as a miss).\n 248│- **SEAM-2 (distinct, still OPEN — the box repro is NOT explained by (B)):**\n 249│  on the busy box, post-clear force-native went UNDELIVERED **immediately**\n    │...\n 274│     lagged the published one; ADR-0014 doc-truth-lives-here demands parity).\n*275│  3. If (B) pins: add the explicit \"empty response = bare `{commit}`\" rule to\n 276│     BOTH files.\n 277│- Provenance notes: legacy claude_skill_owl has NO translate/commit protocol\n 278│  (the contract is an spt-core ADR-0022 invention — no legacy semantics for","missingPaths":["packages","test"],"truncation":{"content":"# docs/\n## BROKER-BRAIN-SPLIT-RESTORATION.md\n 50|\n*51|- **broker** (stable kernel) — holds *only* the un-transferable, must-not-die resources: PTY master fds, spawned harness child processes, listening network sockets, and (per the §B ownership table) the Iroh/QUIC endpoint + conn table. Minimal, versioned local IPC. Almost never updates.\n 52|- **daemon brain** (userspace) — all logic (routing, registry, pulse/psyche loops, manifest parsing, update orchestration). Restarts freely on update; rehydrates from disk and re-attaches to broker-held handles.\n 53|\n 54|Explicit evidence the split was meant to be a **process** boundary:\n...\n 57|- `M3a-PLAN.md`, `M3-PLAN.md`, `M3b-PLAN.md` all name \"the broker **process** + IPC (M3b).\"\n*58|- Spikes 01/03/04/05/06 each **PROVEN** with *two separate binaries* (`spt-spikes/spike-01-broker-handoff`): a PTY child + a live QUIC transfer survive 100× brain restarts, gapless and exactly-once. A spike that proves \"survives a brain restart\" presupposes the broker is a separate process.\n 59|\n 60|### 2.2 What production actually does\n 61|\n...\n 72|\n*73|- The brain cannot restart onto a new binary without killing the in-process broker thread — which would close every PTY, orphan every harness child, and drop every listening socket. So **the no-endpoint-drop self-update pillar is silently unrealized.**\n 74|- `apply` therefore performs an in-process `Brain::handoff` that re-attaches a subscriber within the *same old process* — achieving nothing for a binary swap. `update.rs:234`'s statement that \"the live daemon execs the verified new binary\" is **aspirational; it was never wired** because the process split it depends on does not exist in production.\n 75|- Updates do not run until an unrelated restart/logon (observed live, Section 1).\n 76|\n...\n 182|\n*183|**Decision/observation.** The broker spawns the brain as a **child process** (`Command::spawn`) and they communicate over the existing local socket IPC. **No `exec` is required**, so there is no Windows/Unix divergence. ConPTY (Windows) and forkpty (Linux) handles stay broker-side and never cross to the brain. Spikes 01 and 04 already proved the handoff on both OSes.\n 184|\n 185|**Reason.** This is precisely the uniformity ADR-0004:25 sought when it rejected whole-daemon live FD-passing as \"hard and platform-divergent.\" Spawn-plus-socket sidesteps it entirely.\n 186|\n...\n 227|- **Glossary/model** — `CONTEXT.md` daemon / broker / brain (incl. the one-broker-per-machine clause added this session).\n*228|- **Spikes** — `docs/spikes/SPIKE-01-broker-handoff.md`, `-03-quic-survival`, `-04-forkpty-parity`, `-05-restart-stress`, `-06-idempotent-boundary` (proofs that the two-process handoff works on both OSes).\n 229|- **Key source** — `daemon.rs` (`run`, net consumers, self-heal, digest hub, shellwake, seed-control), `broker.rs` (`OutputLog`, `bind`, `EffectJournal`), `brain.rs` (`BrainState`, `cold_start`/`handoff`/`snapshot`), `applyhost.rs` (`apply_staged`, `connect_retry`, record-before-handoff), `update.rs` (classes, `classify`, `apply_brain_only`), `peerloop.rs` (cadences, supervise-backoff), `lifecycle.rs` (pulse loop), `registryhost.rs`.\n 230|\n 231|---\n...\n 238|- **`REQ-*` registry mints** (registry-first, `traceable-reqs.toml`; minted INACTIVE per rule 5, activated per `RESTORATION-PLAN.md`'s schedule):\n*239|  - **`REQ-HAZARD-BROKER-PROCESS-ISOLATION`** — a brain restart must never drop a hosted endpoint (REQ-UPD-3's real promise). **Evidence [V5]:** an `int` stage productionizing SPIKE-01/03 — a PTY child + a live QUIC conn survive a brain-*process* restart onto a *swapped* binary. REQ-UPD-3 / REQ-DAEMON-2's current `int` evidence (`applyhost.rs:39/77`, `update.rs:246`, `brain_swap.rs`) proves only the *in-process* handoff shape — **mis-evidenced today, re-pointed at plan task D7.**\n 240|  - **`REQ-HAZARD-ROLLBACK-STATE-COMPAT` [V1]** — a brain must not irreversibly migrate durable state before ready-promotion (pre-ready writes stay N-1-readable).\n 241|  - The functional two-process-split realization + the **[V6]** N-1 old-broker × new-brain CI compat test are carried as `RESTORATION-PLAN.md` tasks D1–D7 (the compat test lands as a D7 gate).\n 242|- **KNOWN-HAZARDS** §6.7 (in-process collapse) + §6.8 (rollback-state-compat), with condensed-checklist rows.\n## CONTEXT-MEMORY.md\n 36|\n*37|A lever for an agent behind an **spt-hosted** endpoint to clear and resume itself without stalling. It: (1) triggers a `/clear` (which uses the **Self-resume-commune rule** above — consistent with all other boundaries), (2) thereby captures a commune, and (3) **guarantees a resume signal hits the harness post-clear** so the agent's turn restarts and it resumes work — starting from its immediate next-steps. Spt-hosted-only because the guarantee requires the daemon to own the PTY and inject the post-cl...\n 38|\n 39|### Ownership\n 40|\n## DEFERRED.md\n 11|| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1. **M5 ships presence *resolution* (gossip + most-recently-active API) — its future substrate; the endpoint stays deferred** (user decision 2026-06-04). <!-- [doc->REQ-PRES-1] --> **M5-D6 seam note:** the substrate now exists — the gossiped datum (`Instance.last_active_ms`, riding the registry's epoch lease + replication) and the one...\n*12|| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal. **Note:** distinct from the v1 broker *sensing* user input on a PTY it already holds (spt-hosted) — that is in v1 and is NOT OS-wide input monitoring | Opt-in enhancement after agent-interaction presence proves out |\n 13|| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works. Consent *model* is now designed (CONTEXT §Consent & security gates); **the framework (grant store + escalation + pre-consent flags) ships M5 with this capability id reserved-but-refusing** (user decision 2026-06-04) — the capability lands into it later. Defers with it: the remote-fork arm, cross-node shell spawn, `shell_wake_spawn_anywhere` *behavior* (grant s...\n 14|| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly). **M5 ships the framework with this capability id reserved-but-refusing** (user decision 2026-06-04) | Designed alongside instantiate-anywhere's consent gate |\n 15|| Shell-binary (and harness-binary) sandboxing | Shell concept v1 | A capability toolset bounds what the *agent* can ask, not what the *binary* can do with its OS perms. Baseline stance: running any adapter/shell binary = a disclosed, accepted trust risk for all spt-core users | If/when untrusted third-party shell/harness binaries become common enough to warrant OS-level sandboxing |\n...\n 26|| Trust-row prune verb (`spt subnet prune <node>` or similar): registry GHOST rows now decay (4.10), but the dead identity's TRUST rows persist by design (trust is a user decision) — they cost dead dials every pump tick and clutter status views. BIGNET carries two such rows (09ef831e, both sides) | Ghost-eviction fix (2026-06-06) | Trust mutation = security surface; wants the elevation gate + the M8 noun shapes | M8 CLI nounification |\n*27|| Elevated spt-hosted endpoints — a consented elevation satellite: the broker lives inside the always-unelevated daemon (KH 5.7), so spt-hosted children are unelevated by construction; there is NO silent unelevated→elevated path on either OS (that is UAC/sudo's whole point). An agent needing admin powers is harness-hosted-elevated today (works: TCP listeners + talk-down pipes). The spt-hosted form needs a per-grant CONSENTED elevation: UAC prompt / polkit spawns a small elevated PTY-host satellite ho...\n 28|| ~~AMBIGUOUS render~~ **SHIPPED 2026-06-06** (rode the ghost-eviction fix): resolution refusals render valid copy-paste targets at the wansend boundary (`render_refusal`) — labels preferred, key-prefix on label collision, `subnet:id` for cross-subnet | M7 acceptance run (user 2026-06-06) | Pure render fix at the wansend/CLI boundary; carries into the M8 nounification sweep | DONE |\n 29|| Post-join address seeding: the pairing ceremony holds a live, authenticated connection to the peer, but the peer pumps then re-find each other from scratch via id-only discovery (fresh node ids must propagate to n0 DNS first) — observed live as ~1 min to first `status --nodes` convergence after a join. Seed the pump's address cache from the ceremony connection (both sides) for instant first sync | M7 acceptance run (2026-06-06) | Convergence is correct, just slow on first contact; needs an addr-cache...\n 30|| Clock source priority for TOTP/rendezvous: query a well-known world clock (NTP) FIRST, fall back to local time — machines with skewed local clocks currently derive wrong steps/rendezvous tokens silently | M7 acceptance grill (user 2026-06-06) | Needs an NTP client dep + offset caching + offline fallback semantics; ±1 window covers small skew today | First field report of clock-skew join failures, or M8 polish |\n...\n 43|| **Durable in-daemon alarm scheduler (one-shot deadline machinery — the Q4/V3 deferral)**: the broker/brain restoration (ADR-0018 Q4) fixes the *rule* for one-shot scheduled events — persist the absolute `target-time` at creation; every brain start (update *or* crash) reads it and fires-if-due; **never reset on crash** (a user's \"remind me at 3pm\" is a commitment that must outlive any restart). But it deliberately does **not** build the machinery: the spt-core daemon has **no one-shot consumer today...\n*44|| **Autonomous session-digest freshness + Option-C re-home (M9 ADR-0008 amendment deferral)**: M9 re-founded the digest as an ON-DEMAND projection of normalized session logs (`spt-term::projection`; `spt-daemon::digest::project_endpoint_digest`) — snapshot-pull and the structured-delta-stream CONTRACT ship, deltas driven by pulls / `api digest-entry` pushes. Two pieces deferred, both because they have **no consumer yet** (Shell=M10, GUI/frontend pane, Gateway agent-window — none exist): (a) the **aut...\n 45|| **Two-origin `owl_message` context-tap subtype refinement (M10 ADR-0019 deferral)**: the two-origin merge's owl-message producer (`spt::api::startup` `deliver` closure) taps the live-agent **`emit` chokepoint**, so EVERY delivered frame — including resurfaced **notif** frames and shell-context rows — is recorded as a single coarse `owl_message` context-injection entry (`context_kind:\"owl_message\"`). The data is not wrong (a notif IS injected context), only coarsely labeled; the digest is a glanceab...\n*46|| **W2.5 — attached-presence + Kick-and-attach (M12 picker blue tri-state)**: M12 W2's picker ships online/offline status only; the **blue ■ \"attached\" tri-state** + the **\"Kick `<node>` and attach\"** confirm option were SCHEDULED (operator 2026-06-14, NOT dropped) as a dedicated slice built AFTER W2 lands, then wired back into the picker. Scope: (1) a **broker attach-presence query** — \"who is attached to endpoint E's PTY, by which node/surface\" — derive from the `presence.rs` PresenceLog conn c...\n 47|| **Cross-node shell TUNNEL relay (REQ-SHELL-4 cross-node-on-LAN carry)**: W3 built + proved the opaque byte tunnel **same-node only** (broker-homed `TunnelHub` loopback stream pair + the local control socket; `tunnel_e2e` round-trips `<EVENT`-looking bytes byte-exact). The **cross-node** path does NOT exist: `StreamFamily` (dispatch.rs) has no `Tunnel` variant, `classify_first_line` no tunnel arm, `SHELL_LINK` carries only relink/cmd/drive (no tunnel action), and every tunnel surface (`tunnel_ensure/sen...\n 48|| **Per-connection contiguous-sent cursor — structural guard against a decreasing-floor controller re-take (`REQ-HAZARD-CONTROLLER-WRITER-REORDER` follow-up)**: v0.13.0 P1c fixes the controller-writer reorder while KEEPING handoff's eager subscribe (fix #1 \"drop the subscribe\" was reverted — it's the standalone-resume mechanism), so the decreasing-floor double-take (handoff subscribe@K + serve_attach subscribe@0) is still PRESENT and made safe by: `controller_writer` epoch-gated under `send.lock()` (...\n 49|| **Adapter-manifest-enabled rc VT/mouse mode (`spt rc` scroll vs right-click-paste xor resolution)**: v0.13.0 P1/P1b's Windows `EnableMouseCapture` (for client-originated right-click bracketed paste, 7.18) makes Windows Terminal hand ALL wheel events to rc instead of scrolling its own scrollback; rc forwards the wheel to the harness as SGR reports (7.20) but ONLY when the harness has mouse-reporting on (`enabled && sgr`) — and **Claude Code's REPL never enables mouse tracking** (it has no internal scr...\n## DORMANCY-BUDGET.md\n 15|\n*16|1. binds a real (net-less) broker — the production PTY-hosting path — in a\n 17|   hermetic `SPT_HOME`;\n 18|2. spawns N idle seats through `Brain::spawn_session_pid` (one probe brain per\n 19|   seat, each labeled `budget-<i>`), exactly the shape of a **dormant** seat:\n*20|   PTY master + harness child + output ring, state-preserved and undriven;\n 21|3. settles, then samples each child's **RSS**, **handle/fd count**, and\n 22|   **cumulative CPU** twice across an idle window — the CPU delta is the idle\n 23|   burn of a warm seat;\n 24|4. samples the host process's RSS before/after spawning (the broker-side\n*25|   per-seat overhead: PTY plumbing + output rings);\n 26|5. kills every seat (the **suspended** state — only the on-disk perch record\n 27|   would remain in production) and samples the residual.\n 28|\n...\n 31|count, and `/proc/<pid>/stat` utime+stime. **Limitation:** the sample covers\n*32|the direct PTY child only — a harness that forks a worker tree under-reports\n 33|by the tree's cost (the claude numbers below are the main node process, which\n 34|dominates).\n 35|\n## F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md\n 46|| 5 | STALE-CLI-CONTRACT — bringup recipe passes a removed flag; \"unexpected argument\"; spawn never happens | old recipe on 0.22.0+ | LOUD pre-spawn (good shape) |\n*47|| 6 | RELAUNCH-NEVER-BINDS — 2nd `endpoint run` same id after tree-kill; prints but never binds | **0.22.0 only — FIXED on 0.23.0 by F-028 B1/B4** (perri definitive repro 2026-07-03: tree-kill → same-id re-run re-binds fresh session, live PTY, honest info.json; first check was rig-void, --subnet omitted, caught before reporting) | FIXED-NOTE (not in F-027 scope) |\n 48|| — | UAC-740 elevation spawn-fail | NOT observed (subnet-op elevation only) | out of scope |\n 49|\n 50|Seed-1 correction (accepted): the \"backslash-keyed `hasTrustDialogAccepted`\n...\n 212|3. **Mode-6 — ANSWERED: FIXED** by F-028 B1/B4, 0.23.0 definitive repro\n*213|   (same-id re-run after tree-kill re-binds fresh session, live PTY, honest\n 214|   info.json). Provenance table updated; out of F-027 scope.\n 215|4. **Env capture-at-creation — ANSWERED: CONFIRMED**, promoted from open\n 216|   question to the §3 Half-B binding invariant: `[env] direction=\"read\"` vars\n## F-030-PSYCHE-EPHEMERAL-DESIGN.md\n 70|| 2 | hall-bf-psyche boot-churn ~5 s cadence, ordinal 6491+, synthetic `sess-<pid>-<ts>` ids, no real session ever binds; starts exactly at BIGNET join (7/3) | operator box, real home | adapter can't hold residency |\n*71|| 3 | Poll-verb root repro: `spt ready <id> --once` on 2-subnet home → exit 1, `READY_FAIL … pass --subnet`, empty stdout | perri, clean repro | adapter can't hold residency |\n 72|| 4 | v0.13.2 shim-exit → residency teardown → ready removed → permanent `leg=cli-gate-not-hosted` | hall-bf + perri wake-confirm | residency expectation destructive |\n 73|| 5 | Psyche turns land fine via daemon-driven paths while the resident churns | hall-bf digest (`psyche_download` at every boundary) | per-event shape already proven |\n 74|| 6 | ~12/min re-host cadence NEVER tripped the C3(b) thrash guard (boot records are apparently not ledger boundaries to the guard) | hall-bf | guard blindness (rides this wave) |\n...\n 120|  role/live/project into the psyche-download file whose path fills\n*121|  `{psyche_context_file}` (file-always; non-empty content = fresh preload,\n 122|  0-byte = continue — replaces the former `{psyche_context}` body key) and a\n 123|  fresh psyche session seeds from it. Loud marker on reseed (`PSYCHE_RESEED:{id}`) so a\n 124|  custody-loss loop is visible, plus the W1 failure budget bounds it.\n## F-035-CONFLICT.md\n 34|\n*35|For an **spt-hosted** endpoint (broker owns the PTY, `controllable`, relay-less), the\n 36|idle-edge/parked drain is the **ONLY** carrier for an `active_only` row. Traced every path:\n 37|\n 38|| carrier | `active_only` to spt-hosted? |\n## FAULT-MATRIX.md\n 3|**Why this exists.** ADR-0002 collapsed networking into the one per-machine daemon\n*4|(SERIOUS #6, Stage-A red team): a networking bug and a PTY bug now share a failure\n 5|domain. The collapse is locked — the owed mitigation is this matrix: inject a fault\n 6|into each subsystem **independently**, observe the blast radius (what else breaks)\n 7|and the recovery (what brings it back), and grow the proven rows until every\n...\n 19||---|---|---|---|---|---|\n*20|| 1 | **Brain killed mid-PTY-stream** (logic crash / routine self-update) | Logic halts until restart | Broker, PTY child (pid stable), output log | New brain re-subscribes from cursor; gapless + exactly-once | `spt-daemon/tests/` B2/B9 handoff suite; `tests/idempotent.rs` (crash before-intent / before-effect / after-effect) |\n 21|| 2 | **Brain killed mid-QUIC-stream, receive side** | Logic halts | Broker-owned endpoint, conn, stream, read ring | Resubscribe from durable cursor; ring replays the dead window | `tests/netstream.rs::receiver_brain_restart_is_gapless_and_exactly_once` |\n 22|| 3 | **Brain killed mid-QUIC-stream, send side** | Logic halts | Broker-owned conn/stream + effect journal | Re-drive whole durable op sequence; journal dedups replays (no dup on the wire) | `tests/netstream.rs::sender_brain_restart_redrive_is_exactly_once` |\n 23|| 4 | **Brain dead across peer connect/disconnect** | Presence consumption pauses | PresenceLog ring buffers liveness transitions | Resubscribe from presence cursor; missed transitions replay | `tests/presence.rs` (both tests) |\n...\n 26|| 7 | **Registry feed: corrupt record in the stream** | That record only — skipped | The rest of the feed (decoder does not wedge) | Lease makes loss safe; next update supersedes | `replicate.rs::decoder_survives_chunk_splits_and_corrupt_lines` |\n*27|| 8 | **Net disabled / endpoint bind fails at boot** | No WAN; net frames answer `enabled:false` / typed error | PTY hosting (daemon degrades to net-less broker) | Fix config, restart daemon | `broker.rs::dispatch_net_status` (None arm); `Daemon::run` degrade path |\n*28|| 9 | **Broker process restart (journal survives, conns don't)** | Live conns/streams lost | Effect journal (durable) | Deduped op whose resource is gone → typed \"retry with a fresh op_id\" error; brain re-dials; an `spt rc` operator viewport RECONNECTS on its own — centered banner, full establish re-drive inside a bounded window, then a plain-language give-up (REQ-RC-RECONNECT) <!-- [doc->REQ-RC-RECONNECT] --> | `broker.rs::dispatch_net_dial` / `dispatch_net_stream_open` restart arms; `rc.rs` reconne...\n 29|| 10 | **WAN msg feed replayed** (sender redrive, or receiver brain restart resubscribing from a stale cursor) | None — replayed records dedup | Spool state (each op exactly one row); already-delivered ops | Durable `wan_seen` op-id claims absorb the replay; spool path claims atomically with the row | `tests/wanmsg.rs::receiver_restart_replays_feed_without_double_delivery`; `spool.rs` wan unit tests |\n 30|| 11 | **WAN msg record forges its origin** (payload `origin_node`/`node` field) | None — forgery inert (unknown field) | Access-gate subject (handshake-proven `remote_id_hex`) | None needed (records carry no origin field by design — KH 7.5) | `tests/wanmsg.rs::wan_message_lands_exactly_once_under_transport_origin`; `wanmsg.rs::forged_origin_field_is_inert` |\n*31|| 12 | **Target brain killed mid-remote-attach** (operator typing into the dead window) | Pumping pauses until handoff | Broker-held QUIC stream, session + child, output log, effect journal; the operator's viewport | Successor re-serves the same stream from seq 0 (worst case): replayed input dedups at the PTY-write journal; re-transmitted output dedups at the operator's render cursor — every byte exactly once | `tests/attach.rs::attach_survives_target_brain_restart_exactly_once` |\n 32|| 13 | **Target brain killed mid-file-transfer** (push receive: partial temp file on disk) | Transfer pauses until handoff | Broker-held QUIC stream + ring, the partial temp, the durable progress record (last observed position) | Successor re-serves the same stream from seq 0 (worst case full replay): chunks carry absolute offsets — replayed bytes rewrite in place; the commit is an atomic temp→final rename gated on temp completeness, and a replayed commit dedups against an already-committed final (ne...\n 33|| 14 | **Stream lost mid-context-bundle-sync** (responder dies / wire tears before `Done`) | That one pull errors at the requester | The requester's context store (an incomplete bundle is never fetched — length-gated, scratch file deleted); the responder's store untouched | No resume protocol by design: re-pull fresh — the apply is ancestry-idempotent (an already-joined tip short-circuits; fetched tips quarantine under `refs/spt-sync/` so a partial fetch never clobbers `refs/heads/`) | `tests/sync.rs...\n 34|| 15 | **Partition: concurrent context writes on two nodes** | The file's auto-propagation pauses (conflict surfaced, not propagated) | BOTH versions — local file untouched on each node + the other's version as a durable tracked artifact (hazard 6.6); every other file keeps syncing | Elected reconciler (Active instance's node; lowest-id fallback) runs one bounded Psyche turn → merged write `join(vA,vB)+bump` dominates both parents → propagates as a plain accept, clears artifacts subnet-wide | `test...\n*35|| 16 | **Reconcile turn fails** (harness absent / timeout / garbage or empty output / 6.5-suppressed write) | That file stays in conflict-holding state | Both versions (artifacts + local file) — exactly where they were; the store, the sync loop | Retry at the next sync/activation; a fresher direct write re-merges against the new local version | `spt-live reconcile.rs::failed_turn_preserves_everything` / `suppressed_write_preserves_artifacts`; `turn.rs::empty_stdout_is_an_error` |\n 36|| 17 | **Registry ambiguity during reconciler election** (partition: two nodes both see Active / no Active anywhere) | Two textually-different merges of the same pair may both mint | Neither version is ever lost — merged writes carry joined vectors, so the two merges classify **concurrent** and re-surface as a new conflict pair (detected, not silently last-wins) | Deterministic tiebreak bounds the storm (lowest node id among Actives / non-Offline holders); the re-surfaced pair reconciles on the next pa...\n 37|| 18 | **Compromised relay node serves a tampered update artifact** (bytes corrupted at rest after the relay's own verification) | That one pull ends `Rejected(ArtifactMismatch)` at the puller | The puller's staged release + running binary (nothing staged, nothing applied — no code path from wire bytes to the cache except through `plan_verified`); the rest of the subnet pulls from honest peers | Pull from another peer; the per-node gate (REQ-UPD-2) runs at **every** hop, so one poisoned node never re-p...\n 38|| 19 | **Non-conforming peer offers a rollback/expired/off-channel release** | None — rejected pre-fetch, zero artifact bytes move | The puller's version (monotonic floor holds); the wire (no transfer ever starts) | None needed — the offer-then-fetch shape gates metadata before bytes (REQ-HAZARD-UPDATE-ROLLBACK) | `tests/propagate.rs::rollback_offer_is_rejected_before_any_fetch` |\n...\n 43|| 23 | **Psyche replies with no inbound sender to answer** (alarm-fired turn emits a reply) | That one intent drops with a typed outcome (fail-closed — never broadcast, never guessed) | Every perch (zero deliveries); the notify leg (independent) | The Psyche's next answered turn replies normally | `psyrelay.rs::reply_without_target_is_dropped` |\n*44|| 24 | **Live-Psyche driver loses stdout** (the 7.3 null-stdout shape: detached/`Stdio::null()` regression or a turn that produced nothing) | That turn fails loudly (`TurnError::EmptyOutput`); nothing relays | The relay boundary (no silent success — indistinguishable-from-bug shapes are errors); the spool | Fix the driver / retry the turn | `psyrelay.rs::null_stdout_driver_fails_the_guard`; `spt-live turn.rs::empty_stdout_is_an_error` |\n*45|| 25 | **Runaway Psyche dumps an oversize body** (> 64 KiB in one intent) | That intent drops with a typed outcome | Delivery substrate (never asked to carry it); other intents in the same turn relay normally | None needed (legitimate long bodies chunk at delivery — T3 `EVENT-PART`) | `psyrelay.rs::empty_and_oversize_bodies_drop_typed` |\n 46|| 26 | **Notif feed replayed whole** (re-presented rows after loss/restart — the push-feed's normal recovery) | None — the semilattice join no-ops (`Unchanged` per row, nothing written) | The spool (no duplicates, no state regression) | None needed: replay IS the recovery protocol (full-row re-presentation, no delta bookkeeping) | `tests/notifsync.rs::notif_spools_converge_over_the_wire_and_dismiss_replicates` (replay leg); `notifsync.rs::feed_converges_two_stores_and_dismiss_replicates` |\n 47|| 27 | **Concurrent dismiss + surface on two nodes** (the same notif dismissed on A while B marks it seen/surfaced) | None — the writes commute through the join (OR/union/max); both survive the bidirectional exchange | Both stores converge to identical rows; the dismiss latch (a stale undismissed copy can never un-dismiss) | None needed (no conflict exists to surface — every field is monotone) | `notifsync.rs::concurrent_dismiss_and_surface_commute_across_the_feed`; `spt-store notif.rs::merge_is_idem...\n 48|| 28 | **Untrusted origin injects notif records** (unpaired peer, or a `home`-trusted peer spoofing into `work`) | None — dropped fail-closed at the apply gate, zero rows written; the gate's subject is the handshake-proven stream-table origin, never payload bytes (KH 7.5) | The spool (nothing materialized); every subnet the origin isn't trusted in (trust is per-subnet) | None needed; pair the node to entitle it | `tests/notifsync.rs` (gate-negative leg); `notifsync.rs::untrusted_and_cross_subnet_origin...\n...\n 54||---|---|---|---|---|\n*55|| P1 | **Iroh endpoint task hangs** (not crash — a wedged accept loop) | New inbound conns stall; existing conns + PTY hosting unaffected | PTY side fully isolated (separate threads/runtime) | D9 chaos pass |\n*56|| P2 | **mDNS discovery dies** | LAN dial-by-id fails; direct-addr + relay dials unaffected | Endpoint, conns, PTY | D9 (needs real LAN) |\n 57|| P3 | **Relay unreachable** (n0 outage / air-gap) | WAN dials needing relay fail; LAN/direct unaffected | Everything local | D9 two-host |\n*58|| P4 | **PTY broker drain thread dies** | That session's output stalls | Other sessions, net side | with terminal-wrapper hardening |\n 59|| P5 | **Manifest invocation hangs** (adapter subprocess) | That endpoint's lifecycle op | Daemon scheduler (KH 5.3 timeouts + 7.4 non-blocking) | when daemon hosts N agent loops |\n 60|| P6 | **Registry feed flood** (malicious/buggy peer spams updates) | Bounded by ring caps + lease; CPU cost unmeasured | — | D9 + REQ-SEC-1 outer gate |\n 61|| P7 | **Closed-watcher starvation** (net runtime saturated) | Presence lags behind reality | Conn table eventually consistent | D9 chaos pass |\n## FLAKE-LEDGER.md\n 9||---|------|-------------|-----------|--------|\n*10|| 1 | `spt-daemon brain_restart_survives_gaplessly` | 1× hosted Windows (2026-06-03, hung) | ConPTY stall under hosted-runner load | job `timeout-minutes` bounds it; not seen self-hosted |\n*11|| 2 | `spt-term digest_parses_a_real_pty_stream` | 3× gravity (2026-06-03; 2026-06-04 runs 26957386850, 26966738231) | sprint-collapse assert (`digest.rs`) — PTY input echo and the child's write are **concurrent writers** into the master-read stream; back-to-back `send_line`s let a later line's echo interleave mid-`Write(…)`, breaking the tool regex for that turn | HARDENED (2026-06-04): sends serialized — each line pumped back (adaptive echo-copy count, 2s/line bound) before the next, so no concu...\n 12|| 3 | `spt-daemon sync.rs torn_pull` + `concurrent_writes` | 1× (2026-06-03) | `wait_for_stream` deadline too tight under load | HARDENED: `wait_for_stream_except` 2s→10s; no recurrence |\n 13|| 4 | `spt-live context::write_context_suppresses_stale_llm` | 1× gravity (2026-06-04 run 26967808321) | test calls `write_context` (reads SPT_HOME node identity + epoch stamp) WITHOUT the `with_home` lock — a parallel `with_home` test's env swap + temp-dir teardown races the stamp's epoch write into a deleted home (`NotFound`) | HARDENED (2026-06-04): test wrapped in `with_home` — serializes on the home lock like every other SPT_HOME-touching test |\n 14|| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the cl...\n...\n 18|| 9 | `spt dummy_harness_e2e` (BOTH tests: `endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches` + `endpoint_run_attach_awaits_online_before_attaching`) | 1× kitsubito (2026-07-05, REMOTE-TRUTH Batch-2 re-gate @f2a799e; roles swap run-to-run) | **process-global `set_var` race in a multi-test e2e binary.** Both tests `std::env::set_var(\"SPT_HOME\", own_tempdir)` then do IN-PROC reads (`perch::spt_home`/`adapters_dir`, `registry::register`, psychebin `fs::copy` staging). One file = one test b...\n*19|| 10 | `spt-store registry::tests::concurrent_registration_never_locks` | 1× hfenduleam local gate (2026-07-05, REMOTE-TRUTH Batch-2 bless matrix @3f6f327, fresh worktree, `nextest --workspace --no-fail-fast`) | 16-concurrent-writer SQLite registration gate FAILED at 5.63s — the SQLITE_BUSY load-contention class ALREADY documented at `db.rs:60`: box saturated by the 1588-test full-parallel suite meant a writer couldn't schedule its retry inside the default 5s `registry_busy_timeout`. Isolated ×5: gre...\n 20|| 11 | `spt-daemon::resume resume_mode_brain_spawning_new_sessions_delivers_each` | 1× kitsubito (2026-07-05 run 28768663321 attempt 1, REMOTE-TRUTH [twohost] PR #52) | Phase-B heavy-class timing EOF — \"session exited before the expected output arrived\" (UnexpectedEof) at 62s under the serialized heavy pool. SAME FAMILY as this file's prior Linux-CI flake (7940318, D4-2b: sequential spawn/read hardening) — a spawned session's exit raced the expected output read on the slow shared box. File untouched...\n*21|| 12 | `spt-daemon::input_ack_deadlock input_flood_through_serve_attach_does_not_deadlock_broker` | 1× hfenduleam local gate (2026-07-06, REMOTE-TRUTH F-2 matrix @a21bc6b, `nextest --workspace --no-fail-fast`) | deadline-margin under full-suite load: the flood exchange's `recv_timeout(30s)` (input_ack_deadlock.rs:512) blew under the 1608-test parallel pool + 7 leaky daemons — FAIL at 34.5s vs ~32s natural runtime isolated (the margin is ~3s by design). Subsystem disjoint from the triggering commit (F-...\n 22|| 13 | `spt::bin cli::tests::probe_all_cap_batches_into_windows` | 1× hfenduleam local gate (2026-07-06, stack matrix @0f08fa9 leg-3 rerun) | fast unit (0.52s natural) FAILED at 1.7s under the 1614-test full-parallel pool — probe-batching window assert with a timing component starved under load; subsystem disjoint from the triggering stack (F-2 reporting / E-2 grid title / picker rider). Isolated ×5: green, 0.52–0.55s. Same night as #12 — the full-parallel local matrix under a busy box is the com...\n*23|| 14 | `spt::bin rc::tests::attach_viewport_reconnects_across_a_broker_bounce` | 1× hfenduleam local gate (2026-07-06, WORKER-TRUTH W-3 build @worker-truth, `nextest -p spt --bin spt` full-parallel 360-test run) | **TIMEOUT, not an assertion fail** — SLOW>60…>TERMINATING at the 240s cap under the 360-way `--bin spt` pool. A REAL-broker + broker-bounce reconnect UNIT test (rc.rs:2071) that escaped the HEAVY-class serialization: the Phase-A/B split + `heavy-broker-pty` test-group target integration te...\n## GATEWAY-LIVENESS-DISPATCH.md\n 8|- **`spt endpoint list --json`** → **Suspended** (renders the endpoint via `resource_projection` over the registry snapshot, cli.rs:2855 → `EndpointRowJson.status` = the registry row status).\n*9|- flynn confirmed mobile-gw IS advertised in all 3 subnet registry files (NOT a re-advertise gap — his earlier \"empty by_id\" was a walker error, retracted).\n 10|\n 11|The adapter's suspend-honor poll reads `--json` (`parse_endpoint_status` over `endpoint list --json --show-all`) → sees Suspended → self-suspends the gateway, despite pid-alive + human-ONLINE.\n 12|\n## INJECT-SETTLE-REARM-GATE-VERDICT.md\n 5|## Scope\n*6|Post-0.29.0 field bug: PTY inject HEAD-TRUNCATION recurred on 0.29.0 — a checkpoint-wake payload injected right after `/clear` lost its head (perri screenshot `WindowsTerminal_6iSjya8pMt.png`). doyle root-cause + design ruling; todlando build.\n 7|\n 8|**Root:** `run_inject_worker` gated the W5-A Layer-1 settle-gate behind a worker-local ONE-SHOT (`settled_once`) on a FALSE premise (head-swallow race is startup-only). A mid-session `/clear` re-enters the harness raw-mode input reader → re-creates the pre-settle window, but the one-shot already fired at spawn → `settle_before_inject` skipped → head eaten again; echo-verify (Layer 2) default-OFF for that session → silent + unrecoverable.\n 9|\n*10|**Fix:** `settled_once` one-shot → `probe_unobservable` latch + pure `should_settle(attempt, probe_unobservable) = attempt>1 || !probe_unobservable`. Re-settle before EVERY delivery on an OBSERVABLE (echoing/interactive) PTY (bug-prone class, cheap); latch-skip the steady-state settle ONLY where the probe is UNOBSERVABLE (non-echoing ConPTY — no reattach race, full-deadline cost). The settle's own bool return discriminates the class, latched on attempt 1; a re-drive always settles. Mints REQ-HAZARD-I...\n 11|\n 12|## Diff review — FAITHFUL\n*13|Matches the design ruling exactly: latch semantics, pure gate, first-attempt-only latch update, re-drive-always-settles. KH 7.37 + traceable REQ minted with all three stages (`doc`/`impl`/`unit`) tagged on real evidence. RED-first unit `should_settle_rearms_on_observable_pty` present and correct. No int edit — safety-checked: `inject_control_wedge` mocks (`findstr`/`cat`) never answer the DSR probe → settle times out → `probe_unobservable` latches true after delivery 1 → identical to the old one-...\n 14|\n 15|## Both legs GREEN\n*16|- **Windows (ConPTY, isolated worktree + fresh target):** clippy `--workspace --all-targets -D warnings` = 0; `traceable-reqs check --json` = 0; nextest `--workspace` = **1707/1707** (9 leaky, 1 skip), exit 0. `should_settle_rearms_on_observable_pty` PASS.\n*17|- **Linux (kitsubito/forkpty, bundle-deploy @ddf7f8e):** clippy = 0; nextest `--workspace` = **1695/1695** (2 leaky, 1 skip), exit 0. `should_settle_rearms_on_observable_pty` PASS.\n 18|\n 19|## GATE-HOLD investigated → CLEARED (env contamination, not code)\n 20|First Windows full-suite run REDed on `inject_control_wedge::wan_arrival_to_idle_spt_hosted_injects_with_no_hook_poll` (`pending_spool_rows: left=1 right=0`, `INJECT_HEAD_LOSS_SPOOL` fired). Discriminated:\n 21|- Deterministic 5/5 isolated FAIL → not a flake.\n*22|- Root: `SPT_INJECT_VERIFY_ECHO=1` was exported in the gater's interactive live-agent shell (the live box arms Layer-2 echo-verify), inherited by the gate script → `echo_capable=true` → on Windows ConPTY the choreo child puts a headless line on the ring → Layer-2 false-positive head-loss → respool → `pending=1`.\n 23|- Proven env-driven: same test PASSES with `SPT_INJECT_VERIFY_ECHO` unset (deterministic both ways). Linux passed because its ssh env is clean.\n*24|- **CI-safe:** the var is **Process-scope only** (User/Machine scope empty) — the GitHub Actions runner service (hfenduleam, separate process tree) does NOT inherit it → PR CI runs clean. The clean-env full re-run (1707/1707) = CI's actual condition.\n 25|- The fix touches only Layer-1 settle; Layer-2 echo-verify is untouched W5-A code. The failure is orthogonal to the change.\n 26|\n 27|## Follow-up SEED (non-blocking; filed to todlando)\n## KNOWN-HAZARDS.md\n 70|### 2.5 Daemon-hosted endpoints have no dedicated liveness PID\n*71|- **Failure:** the sister evaluates Psyche/perch liveness via a dedicated process PID — the wrapper's own pid in `info.json`, checked with `is_process_alive`. Under ADR-0004 the Psyche (and any spt-hosted Self) is a **loop inside the daemon**, not a separate process: it holds no dedicated pid, and its `claude`/summarizer subprocess is ephemeral (spawned per pulse/commune, then exits). If a daemon-hosted perch's `info.json` carries the **daemon's** pid, then *every* hosted endpoint shares one pid, and `...\n 72|- **Invariant:** for **daemon-hosted** perches (Psyche; spt-hosted Self), liveness is the **daemon's authoritative in-memory endpoint table + a `status` field** on `info.json` (`online|offline|…`), **never** `is_process_alive(info.pid)`. `info.pid` for a daemon-hosted perch is at most a *hosted-by-daemon* marker (the daemon pid), not a liveness signal; registry stale-clean for these rows keys on the daemon's endpoint table, not per-row pid. This reuses the pattern already specified for **Shells** (`inf...\n 73|- **spt-core mapping:** the **M1/M2a interim** model keeps the Psyche/listener a real per-process owner (the `api listen` process), so its per-pid liveness (`deliver::is_online` → `info.read_pid` → `proc::is_process_alive`; `registry::clean_stale_entries`) is correct *interim*. **M3 daemon consolidation replaces it** with daemon-authoritative liveness for hosted perches. Keep the liveness check behind one resolver (mirrors `resolve_address` stale-clean) so the M3 swap is localized — do **not** let ...\n 74|- **Sister cite:** `src/live/wrapper/orphan.rs` (wrapper-pid liveness); `src/common/list_filter.rs:168-175` (pid-classify); spt-core `crates/spt-store/src/{proc.rs,registry.rs}` + `crates/spt-msg/src/deliver.rs::is_online`.\n...\n 80|### 3.1 Ephemeral perch cleanup on every `ring` exit path\n*81|- **Failure:** `ring` creates an ephemeral perch; early-exit paths (no-perch, empty-msg, timeout) skip cleanup → stale dirs accumulate.\n 82|- **Invariant:** every code path that creates an ephemeral perch cleans it before exit; exception: if the caller already had an active perch, do not treat as ephemeral and do not clean up.\n 83|- **spt-core mapping:** `ring` semantics carry; the daemon owns ephemeral-perch lifecycle, so a single guaranteed-cleanup (drop guard / RAII) is achievable in-process.\n 84|- **Sister cite:** `src/owl/ring.rs:58-294`.\n...\n 199|\n*200|### 5.5 ConPTY withholds output until DSR is answered  `[REQ-HAZARD-CONPTY-DSR]`\n*201|- **Failure:** a broker reading a ConPTY master sees only the 4-byte startup query `ESC [ 6 n` and then nothing — the child looks hung/silent but is producing output normally. ConPTY blocks all child stdout until the terminal answers the cursor-position query.\n*202|- **Invariant:** every ConPTY reader auto-answers DSR (`ESC [ 6 n` → write `ESC [ 1;1 R`, or a real cursor position) on the PTY writer. Secondary: a ConPTY master does not EOF while the writer is held, so read loops drain on a thread and never gate exit on a blocking `read()`.\n*203|- **spt-core mapping:** `spt-term` broker PTY reader (ADR-0004). Brand-new to spt-core — not in the sister project (it never hosted ConPTY directly).\n 204|- **Source:** Spike #1 (`docs/spikes/SPIKE-01-broker-handoff.md`); reproduced with both a Rust child and `cmd.exe`.\n 205|\n 206|<!-- [doc->REQ-HAZARD-DETACHED-PIPE-INHERIT] -->\n...\n 249|\n*250|<!-- [doc->REQ-HAZARD-WIN-PTY-PROGRAM-RESOLVE] -->\n*251|### 5.12 Native-PTY spawn of a bare program runs the wrong (non-PE) file on Windows  `[REQ-HAZARD-WIN-PTY-PROGRAM-RESOLVE]`\n*252|- **Failure:** `portable-pty`'s ConPTY spawn resolves a bare program name with a `which` that takes the FIRST `PATH` match. A node/npm CLI installs as BOTH an extensionless shebang shim (`ccs`, for Git Bash) and a Windows launcher (`ccs.cmd`) in the same dir; portable-pty picks the extensionless `ccs`, and `CreateProcessW` then tries to execute that non-PE file and fails with **os error 193** (\"%1 is not a valid Win32 application\"). Live failure: `spt endpoint run claude-spt:ccs` → `CreateProcessW C:\\n...\n*253|- **Invariant:** spt-term resolves the program ITSELF before handing it to `CommandBuilder`, bypassing portable-pty's `which`. A bare name is searched over `PATH` × `PATHEXT` (whose default order already prefers `.EXE`/`.COM` over `.BAT`/`.CMD`), then an extensionless fallback. A non-PE target is wrapped in its interpreter: `.cmd`/`.bat` → `cmd.exe /d /c <path>`, `.ps1` → `powershell -NoProfile -File <path>` (the wrap args precede the caller's args); a real executable spawns directly; an unresolvabl...\n*254|- **spt-core mapping:** `spt_term::winprog::{resolve_for_pty, resolve_in}` (the pure PATHEXT-precedence kernel + the Windows env wiring), wired into `spt_term::pty::PtySession::spawn_program_in`. Unit: `resolve_in` precedence (`.cmd`-over-shim, `.exe`-direct, explicit-extension, path-order, passthrough) [`winprog.rs`].\n 255|- **Source:** field diagnosis 2026-06-16 (operator dogfood, `claude-spt:ccs` bringup) — doyle.\n 256|\n 257|<!-- [doc->REQ-HAZARD-PERCH-RECORD-POWER-LOSS] -->\n...\n 300|\n*301|### 6.3 Cascade-wipe guard: never delete a parent hosting non-empty children\n 302|- **Failure:** `doctor --fix` deletes a top-level perch that still hosts in-flight nested Worker/Psyche perches.\n*303|- **Invariant:** before hard-delete, check for non-empty nested children; if present, soft-clean only and surface the path.\n 304|- **spt-core mapping:** any destructive maintenance command must check for live child instances first.\n 305|- **Sister cite:** CHANGELOG v1.11.20 Phase 35.1.\n 306|\n...\n 325|### 6.7 Broker and brain MUST be separate processes (in-process collapse silently breaks no-endpoint-drop update) `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`\n*326|- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old c...\n*327|- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/0...\n 328|- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.\n 329|- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.\n 330|<!-- [doc->REQ-HAZARD-BROKER-PROCESS-ISOLATION] -->\n...\n 346|- **Invariant:** the daemon-hosted multi-session event loop must not consume a session's OUTPUT inside another session's blocking wait. The **live-agent adapter milestone must restructure the brain event pump** so command/`spawn` is non-blocking (a single demux loop owns `read_event` and routes every frame to its session's consumer), OR a blocking wait must re-queue/route the frames it reads for other sessions rather than dropping them.\n*347|- **spt-core mapping:** **unreachable today** — the supervised daemon brain hosts no PTY sessions and spawns none; a single-session seat (legacy, empty map) has no \"other session\" to starve. Surfaces the moment daemon-hosted sessions land (the live-agent adapter), which must rebuild the blocking `read_event` loop regardless (N interactive sessions cannot share one blocking reader). Recorded so that redesign inherits the constraint rather than rediscovering it. No machinery now (would be untested dead c...\n 348|- **Origin:** surfaced by the D4-2b resume-harness CI flake root-cause (agents `todlando` + `doyle`, 2026-06-10); sibling of `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]` 6.7.\n 349|\n 350|### 6.10 Phase-significant loop timing must be a durable absolute-deadline grid, not phase-relative sleep `[REQ-HAZARD-BROKER-PROCESS-ISOLATION]`\n...\n 375|### 7.2 Idempotent delivery across brain restart  `[REQ-HAZARD-RESTART-IDEMPOTENT]`\n*376|- **Failure:** broker queues, spool rows, PTY injection, remote streams, and file transfers cross the broker↔brain boundary; a brain restart duplicates or drops a side effect.\n*377|- **Invariant:** every side effect crossing the boundary carries a durable ID + replay rule; replay after restart is exactly-once / idempotent. Crash the brain before/after spool write, before/after PTY write, mid-transfer, mid-registry update — no dup, no drop.\n 378|- **spt-core mapping:** broker↔brain IPC (ADR-0004), self-update handoff.\n 379|- **Source:** codex Stage A #14.\n 380|\n...\n 403|- **Failure:** the peer pump is a SINGLE thread driving every leg (registry/notif/sync/update) against every peer over ONE brain-IPC client. Its reply reads (`net_open_stream`, `net_stream_send`, `net_dial`, and the sync/update pull `read_event` loops) were `loop { read_event() }` with no deadline. When a peer's QUIC path black-holes, the broker's stream-open/send awaits the dead peer and never sends the reply, so the brain's `read_frame` blocks FOREVER and the pump freezes mid-round. The heartbeat (loop...\n*404|- **Invariant:** in PUMP mode the brain carrier is **SPLIT at construction** — a dedicated `pump-ipc-reader` thread does blocking `read_frame` on the `RecvHalf` and forwards each framed result down a channel; the main thread writes on the `SendHalf` and reads with `Receiver::recv_timeout`. Every IPC reply read is bounded by a per-call **total-wait** deadline (`PUMP_PEER_IO_TIMEOUT` = 30s, > any legitimate round-trip, < the 60s QUIC idle; re-armed on stream progress for the streaming pull legs so a heal...\n 405|- **spt-core mapping:** `Brain::cold_start_pump` (splits the carrier + arms the deadline) / `BrainConn::Split` (the `SendHalf` + reader-thread channel) / `call_deadline` / `read_event_until` / `read_frame_until` (the `recv_timeout` dispatch), `pump::run_peer_pump` (connects in pump mode) + `pump::peer_outcome` (the tier-split), the `request_sync`/`request_update` pull loops (deadline re-armed on progress). The broker-side half — the broker must never make a brain wait unbounded on a QUIC op (bound the ...\n 406|- **Source:** field diagnosis 2026-06-11 (the 2.2h hfenduleam wedge); doyle ruling A-now / B-deferred. The stall warning (M8 decision 23) was the band-aid; this is the fix. The B-half landed v0.8.3 (§7.8) after the 2026-06-16 recurrence.\n 407|\n...\n 409|<!-- [doc->REQ-HAZARD-VIEWER-ISOLATION] -->\n*410|- **Failure:** the W2.5 controller/viewer model lets ANY number of read-only `--view` attachers ride one session's broker `OutputLog`. The single drain thread fans each output chunk to every attacher. If a viewer's socket is fanned out with a **blocking** write under the log lock (the controller's authoritative path), one wedged viewer (a slow terminal, a black-holed WAN peer, a hostile non-reader) stalls the drain — freezing the controller's stream and backing up the PTY child. A single watcher must n...\n 411|- **Invariant:** the drain writes the **controller** on the authoritative blocking bounded path (it alone advances `delivered_through`), but each **viewer** gets an **isolated bounded SPSC queue + a dedicated writer thread**; the drain `try_send`s under the log lock and **evicts** any viewer whose queue is `Full` (fell behind the live stream) or `Disconnected` (its writer died on a dead socket) — the drain thread **never touches a viewer socket**, so no viewer write can backpressure it. A **soft cap** ...\n 412|- **spt-core mapping:** `OutputLog::append` (controller blocking + `viewer_send_evicts` `try_send` fan-out), `OutputLog::add_viewer` (bounded `sync_channel(VIEWER_CHANNEL_DEPTH)` + `viewer_writer` thread), `MAX_VIEWERS` soft cap, `ViewerSink`. Unit: `viewer_overflow_or_disconnect_evicts_never_blocks`. Int: `wedged_viewer_does_not_stall_controller` (a non-reading viewer is evicted while the controller keeps receiving past a 200KB burst).\n 413|- **Source:** M12 W2.5 controller/viewer model (doyle ruling 2026-06-14, Q1).\n## MANIFEST.md\n 129|<!-- [doc->REQ-PSYCHE-CONTEXT-FILE-INDIRECTION] -->\n*130|**Psyche-download — `{psyche_context_file}` (file-always, replaces `{psyche_context}`).** The composed Psyche mind rides a **file**, never the command argv: before each turn spt-core writes the mind into the nested psyche perch dir and fills a single **`{psyche_context_file}` = that path** (argv-cap-immune — a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / re...\n 131|\n 132|<!-- [doc->REQ-PSYCHE-NESTED-RESOLUTION] -->","truncated":true,"truncatedBy":"bytes","totalLines":531,"totalBytes":79683,"outputLines":276,"outputBytes":51077,"lastLinePartial":false,"firstLineExceedsLimit":false},"linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}},"truncation":{"direction":"head","truncatedBy":"bytes","totalLines":531,"totalBytes":79683,"outputLines":276,"outputBytes":51077,"shownRange":{"start":1,"end":276},"nextOffset":277}}},"isError":false,"timestamp":1784247349041}}
{"type":"message","id":"0c784545","parentId":"79600b4d","timestamp":"2026-07-17T00:15:49.468Z","message":{"role":"toolResult","toolCallId":"call_cmWv9B3s1H7KrcLCN6C7jbFV|fc_099a7567dadef391016a597434e84c819bb43a6da81249f9c2","toolName":"grep","content":[{"type":"text","text":"# CHANGELOG.md\n 42|\n*43|## [0.34.0] - 2026-07-16\n 44|\n 45|A stability fix for live remote sessions during a daemon update.\n 46|\n...\n 56|\n*57|## [0.33.0] - 2026-07-16\n 58|\n 59|A performance and identity release. The commands that list endpoints and choose\n 60|a run now answer in a fraction of a second instead of many seconds, `spt whoami`\n\n# docs/\n## REDISPATCH-TRUTH-TRIAGE.md\n 2|\n*3|> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first\n 4|> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:\n 5|> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n 6|> (hertz source-level, doyle seam-verified same day). Decision record:\n...\n 41|2/3): 4/5 endpoints frozen across both 2026-07-16 doyle refreshes\n*42|(deployah-recovery + the v0.33.0 apply) + lia on the update;\n 43|`DISPATCH:N:Failed` lines with low/historical stream ids in fresh\n 44|generations = the indiscriminate-replay supporting evidence.\n 45|\n## RELEASE-RUNBOOK.md\n 52|     with no gap. Inserting the new heading with an editor whose anchor spans the\n*53|     previous heading can silently *consume* that heading (v0.34.0 did this to\n*54|     `## [0.33.0]`), folding the prior release's body into the new section — and\n 55|     since the body is published verbatim, the release notes then carry two\n 56|     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n 57|3. **Tag**: `git tag v0.X.Y && git push origin v0.X.Y`. This triggers:\n\n# docs-site/src/\n\n## harness-contract/\n### api.md\n 353|\n*354|### `spt whoami` — the identity verb *(identity-only since v0.33.0)*\n 355|\n 356|<!-- [doc->REQ-WHOAMI-IDENTITY-ONLY] -->\n 357|The bounded-time \"which endpoint am I?\" answer for hooks and adapter glue:\n### integration-checklist.md\n 317|  suspended rows honor `--all` the same way. `spt whoami --json` emits its OWN\n*318|  identity-only shape *(since v0.33.0 — previously this list shape)*:\n 319|  `{id, state?, ready?, alive?, unbound?, description?}`, or `{\"id\": null}` +\n 320|  exit 1 when the session owns no endpoint. It never derives projects — the\n 321|  bounded-time identity verb for hook paths (see the [API reference](api.md)\n\n## reference/\n### json-shapes.md\n 96||---|---|\n*97|| `endpoint list` | `{ self, subnets[], local[] }` — `self`: `{id, status, ready, alive, unbound, description, psyche_host_error, translation_fault?}`; `subnets[]`: `{name, endpoints[]}` where each endpoint is `{id, node, node_label, status, resources, endpoint_type?, project?}`; `local[]`: `{id, state, address, ready, alive, unbound, project?}`. *(Since v0.33.0 the local `project` field reads the daemon-maintained project index — answers are immediate and may lag a just-changed project by moments; a...\n*98|| `whoami` | `{ id, state?, ready?, alive?, unbound?, description? }` — identity-only *(since v0.33.0; previously the `endpoint list` shape)*: the calling session's own endpoint, or `{\"id\": null}` + exit 1 when the session owns none. Never derives projects — the bounded-time identity verb for hooks. |\n 99|| `endpoint digest` | `{ turns[] }` — see [above](#session-digest--endpoint-digest---json) |\n 100|| `endpoint description show` | `{ id, description }` |\n 101|| `endpoint role` | `{ id, role }` |\n*102|| `api endpoint-info [<id>]` | `{ id, endpoint_type, adapter, local_node:{label,key}, attached_node:{label,key}\\|null, controlled, project, cwd, subnets[] }` (always JSON) *(since v0.33.0 `project` is index-fed — bounded time, safe on hook paths)* |\n*103|| `daemon status` | `{ running, pid, net_up, pump_heartbeat_ms, managed_by, managed_active, subnets[], local_endpoints[], broker_image?, broker_stale?, stall_evict_count?, stall_evict_last_ms?, project_index? }` — `project_index` *(since v0.33.0)* is the index writer's health block: `{generated_ms, source_generation, pending_refresh, last_run_ms, last_duration_ms, last_error?, endpoints, projects, cwds, cwd_cache_hits, cwd_cache_misses, stale_reads, repairs, last_cycle:{branch_enumerations,tree_scans,d...\n 104|| `subnet status [--nodes]` | `{ daemon_running, subnets[] }` — each `{name, node_count, endpoint_count, nodes[]}` |\n 105|| `subnet show-code` | `{ subnet, code, otpauth_uri? }` |\n 106|| `notif list` | `{ notifs[] }` — each `{notif_id, subnet, kind, state, from_id, head}` |\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":".","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":12,"fileCount":6,"files":["CHANGELOG.md","docs/REDISPATCH-TRUTH-TRIAGE.md","docs/RELEASE-RUNBOOK.md","docs-site/src/harness-contract/api.md","docs-site/src/harness-contract/integration-checklist.md","docs-site/src/reference/json-shapes.md"],"fileMatches":[{"path":"CHANGELOG.md","count":2},{"path":"docs/REDISPATCH-TRUTH-TRIAGE.md","count":2},{"path":"docs/RELEASE-RUNBOOK.md","count":2},{"path":"docs-site/src/harness-contract/api.md","count":1},{"path":"docs-site/src/harness-contract/integration-checklist.md","count":1},{"path":"docs-site/src/reference/json-shapes.md","count":4}],"truncated":true,"displayContent":"# CHANGELOG.md\n 42│\n*43│## [0.34.0] - 2026-07-16\n 44│\n 45│A stability fix for live remote sessions during a daemon update.\n 46│\n   │...\n 56│\n*57│## [0.33.0] - 2026-07-16\n 58│\n 59│A performance and identity release. The commands that list endpoints and choose\n 60│a run now answer in a fraction of a second instead of many seconds, `spt whoami`\n\n# docs/\n## REDISPATCH-TRUTH-TRIAGE.md\n  2│\n *3│> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first\n  4│> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:\n  5│> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n  6│> (hertz source-level, doyle seam-verified same day). Decision record:\n   │...\n 41│2/3): 4/5 endpoints frozen across both 2026-07-16 doyle refreshes\n*42│(deployah-recovery + the v0.33.0 apply) + lia on the update;\n 43│`DISPATCH:N:Failed` lines with low/historical stream ids in fresh\n 44│generations = the indiscriminate-replay supporting evidence.\n 45│\n## RELEASE-RUNBOOK.md\n 52│     with no gap. Inserting the new heading with an editor whose anchor spans the\n*53│     previous heading can silently *consume* that heading (v0.34.0 did this to\n*54│     `## [0.33.0]`), folding the prior release's body into the new section — and\n 55│     since the body is published verbatim, the release notes then carry two\n 56│     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n 57│3. **Tag**: `git tag v0.X.Y && git push origin v0.X.Y`. This triggers:\n\n# docs-site/src/\n\n## harness-contract/\n### api.md\n 353│\n*354│### `spt whoami` — the identity verb *(identity-only since v0.33.0)*\n 355│\n 356│<!-- [doc->REQ-WHOAMI-IDENTITY-ONLY] -->\n 357│The bounded-time \"which endpoint am I?\" answer for hooks and adapter glue:\n### integration-checklist.md\n 317│  suspended rows honor `--all` the same way. `spt whoami --json` emits its OWN\n*318│  identity-only shape *(since v0.33.0 — previously this list shape)*:\n 319│  `{id, state?, ready?, alive?, unbound?, description?}`, or `{\"id\": null}` +\n 320│  exit 1 when the session owns no endpoint. It never derives projects — the\n 321│  bounded-time identity verb for hook paths (see the [API reference](api.md)\n\n## reference/\n### json-shapes.md\n  96│|---|---|\n *97│| `endpoint list` | `{ self, subnets[], local[] }` — `self`: `{id, status, ready, alive, unbound, description, psyche_host_error, translation_fault?}`; `subnets[]`: `{name, endpoints[]}` where each endpoint is `{id, node, node_label, status, resources, endpoint_type?, project?}`; `local[]`: `{id, state, address, ready, alive, unbound, project?}`. *(Since v0.33.0 the local `project` field reads the daemon-maintained project index — answers are immediate and may lag a just-changed project by moments; a...\n *98│| `whoami` | `{ id, state?, ready?, alive?, unbound?, description? }` — identity-only *(since v0.33.0; previously the `endpoint list` shape)*: the calling session's own endpoint, or `{\"id\": null}` + exit 1 when the session owns none. Never derives projects — the bounded-time identity verb for hooks. |\n  99│| `endpoint digest` | `{ turns[] }` — see [above](#session-digest--endpoint-digest---json) |\n 100│| `endpoint description show` | `{ id, description }` |\n 101│| `endpoint role` | `{ id, role }` |\n*102│| `api endpoint-info [<id>]` | `{ id, endpoint_type, adapter, local_node:{label,key}, attached_node:{label,key}\\|null, controlled, project, cwd, subnets[] }` (always JSON) *(since v0.33.0 `project` is index-fed — bounded time, safe on hook paths)* |\n*103│| `daemon status` | `{ running, pid, net_up, pump_heartbeat_ms, managed_by, managed_active, subnets[], local_endpoints[], broker_image?, broker_stale?, stall_evict_count?, stall_evict_last_ms?, project_index? }` — `project_index` *(since v0.33.0)* is the index writer's health block: `{generated_ms, source_generation, pending_refresh, last_run_ms, last_duration_ms, last_error?, endpoints, projects, cwds, cwd_cache_hits, cwd_cache_misses, stale_reads, repairs, last_cycle:{branch_enumerations,tree_scans,d...\n 104│| `subnet status [--nodes]` | `{ daemon_running, subnets[] }` — each `{name, node_count, endpoint_count, nodes[]}` |\n 105│| `subnet show-code` | `{ subnet, code, otpauth_uri? }` |\n 106│| `notif list` | `{ notifs[] }` — each `{notif_id, subnet, kind, state, from_id, head}` |","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784247349468}}
{"type":"message","id":"8c32a4d3","parentId":"0c784545","timestamp":"2026-07-17T00:15:49.471Z","message":{"role":"toolResult","toolCallId":"call_oyupfYAvzW90rURzDs6pH2wl|fc_099a7567dadef391016a597434e850819ba8e2f05e9c6beafe","toolName":"grep","content":[{"type":"text","text":"# CONTEXT.md\n 387|\n*388|**redispatch** (REDISPATCH-TRUTH, ADR-0038): the fresh brain's reconstruction of target-side attach workers over broker-held peer streams after a **brain cycle** (`spt daemon refresh` / `spt update` apply — the PTYs and QUIC streams survive; the brain-owned serve workers do not). Redispatch eligibility is **lifecycle-gated**: finished/terminal stream rows are retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach — the KNOWN-HAZARDS 7.41 frozen-PTY steal/clear...\n*389|_Avoid_: conflating redispatch with msg dispatch or session-cursor resume; filtering replay by origin identity (the legitimate same-`by` successor re-take must keep working — lifecycle is the discriminator).\n 390|\n 391|<!-- [doc->REQ-RC-KEY-VT-TRANSLATE] -->\n 392|**rc keyboard input (Windows VT translation, v0.13.0 bug 2).** On **Windows** an interactive `spt rc` console reads crossterm **key events** and translates each to **standard xterm VT** (`translate_key_event`) — arrows / Home / End / PgUp/Dn / Insert / Delete / F-keys + modifiers all reach the harness as the universal terminal contract (**agnostic**, NOT win32-input-mode; the legacy console delivers those keys as events, not bytes, so the old byte-pump left them DEAD). **Unix passes through** (its raw-...\n...\n 582|\n*583|**daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previously required a full daemon bounce. <!-- [doc->REQ-DAEMON-REFRESH] -->\n 584|\n 585|**project index** — a node's endpoint→project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers — list, picker, endpoint-info, hoo...\n 586|\n\n# M12-W5-PLAN.md\n 19|  first, then `SUBNET <name>` groups with endpoint rows. `--local` short-circuits to\n*20|  `cmd_list_local`. So \"alias for endpoint list\" is mostly a re-dispatch.\n 21|- **Self endpoint description** is reachable: `spt_store::info::read_info(&perch_path)\n 22|  .resources` where `perch_path = perch::resolve_perch_path(&id, ParentHint::Infer)` —\n 23|  exactly what `cmd_description` (cli.rs:1352, `DescriptionCmd::Show`) reads. `None` =\n...\n 31|## The DELTA\n*32|1. Re-dispatch `cmd_whoami` to the endpoint-list view (SELF pinned).\n 33|2. Augment the **SELF pin line** (in whoami, and arguably in `endpoint list` too) with the\n 34|   Self endpoint's authored description.\n 35|3. Reconcile the `how-to: ready` doc + register the REQ.\n...\n 69|  `info::read_info`. Pure-ish render unit-tested over a fixture roster row + blurb.\n*70|- **T5.1b** re-dispatch `cmd_whoami` to the chosen shape; reconcile the `how-to: ready`\n 71|  doc line + CHANGELOG.\n 72|- Sweep: full suite green · clippy -D · `traceable-reqs check` EXIT=0. W5 gate (doyle).\n 73|  Then **W2.5** (attach/kick) → full M12 gate → unblock perri.\n\n# W3-DESIGN-GATE.md\n 30|- **W3c manifest refresh.** A `BrainLifecycle` method (`lifecycle.rs:61` struct; `manifest: Manifest` field at `:70`, `runtime: ManifestRuntime` at `:62`, built in `with_config` `:93`/`:120-129`) that re-reads the new on-disk manifest and rebuilds BOTH `manifest` and `runtime` (`ManifestRuntime::with_install_dir`). Propose signature + call site.\n*31|- **W3d daemon-apply IPC.** CLI keeps fetch+verify (no new daemon HTTP). For an adapter with running resident children, CLI hands **apply** to the daemon over a new IPC command (where on the api/broker IPC surface?). Daemon apply **per affected endpoint**: stop resident children (translation; Psyche per (c)) → W3b swap → W3c refresh → restart resident (Psyche re-spawns from its new own-copy). Endpoint **not** running → CLI swaps directly (no lock, no cache, no round-trip). Route in `cmd_adapter_u...\n 32|- **Bounded + brain-parity + partial-failure (BINDING).** Stop→restart MUST be bounded (`join_bounded` / kill-bounded; **never ride a 240s backstop**). The endpoint (`BrainLifecycle`/broker) **never restarts** — only the resident child cycles. A mid-swap failure must **not strand** the endpoint (stopped binary + half-swapped files): restart-old / atomic-ish on failure — specify the recovery.\n 33|\n 34|## W3e — int keystone, NO mocks (the ship-blocker)\n\n# W3-DESIGN-PROPOSAL.md\n 16|\n*17|**Consequence:** W3a/W3d (cycle the translation child) belong in the **broker** and are clean. W3c (manifest refresh) **cannot** be \"re-clone into the running BrainLifecycle\" as ADR-0025 states — the live instance is thread-owned and IPC-unreachable. It needs a **shared refresh handle** the brain registers at host time. This is the biggest correction below.\n 18|\n 19|---\n 20|\n...\n 121|\n*122|- **ADR-0025 amendment** — add the **third axis** to the resident/ephemeral split: *ephemeral-but-install-dir-locking → run from a per-endpoint own copy*. Correct the two errors below; document (a)/(c)/broker-as-registry/manifest-refresh-via-shared-handle.\n 123|- **REQ-ADAPTER-LIVE-UPDATE** → `[\"doc\",\"impl\",\"unit\",\"int\"]` (activate at W3 build).\n 124|- **New `REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP`** (§4) — doyle confirms id/shape before mint.\n 125|- Tags `[impl/unit/int->REQ-ADAPTER-LIVE-UPDATE]` + `[unit/int->REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP]` on real evidence.\n...\n 140|\n*141|`W3a` (broker query + `HostedSession{adapter,install_dir}`) → `W3b` (pure `sha256`-CRC swap helper, unit-tested) → `W3c` (`BrainLifecycle` `Arc<RwLock>` + `refresh_manifest` + brain-registers-handle) → **(a)** stop-path scoped-reap + **(c)** psyche own-copy → `W3d` (`KIND_ADAPTER_APPLY` + `dispatch_adapter_apply` + CLI routing/decision) → `W3e` (NO-mocks int + Windows-lock regression).\n 142|\n*143|**No impl until doyle confirms** — especially: the §2 manifest-refresh mechanism, the §3 own-copy dir location, the §4 new REQ-HAZARD id/shape, and the §6 broker-IPC-vs-seedmap call.\n\n# W3-GATE-VERDICT.md\n 7|1. **W3a — reuse the broker `sessions` map as the resident registry** (no parallel structure). CONFIRMED. Add `HostedSession{adapter, install_dir}` + the 2 query helpers.\n*8|2. **W3c — manifest refresh via a shared `Arc<RwLock<Manifest>>` / `Arc<RwLock<ManifestRuntime>>` handle the brain registers at host into a broker-side per-endpoint map.** CONFIRMED as primary. Binding cautions:\n 9|   - **(i) Minimal read-lock scope** — every one of the 4 read sites must `read() → clone what it needs → drop the guard` BEFORE any spawn / blocking call / pulse-body work. A read guard held across `spawn_psyche`/`fire_echo` while an apply wants the write lock is the failure mode. Prove it (a quick comment at each site).\n 10|   - **(ii) Handle lifecycle** — the broker-side handle-map entry MUST be inserted at host and **removed on session/host teardown**, and **re-registered on brain restart** (the brain re-hosts; the Arc must not dangle or point at a dead brain's state). No stale handles.\n 11|   - **(iii) Fallback** — if the read-lock scoping across the 4 sites proves gnarly, the escape hatch is a brain-owned **reload-on-dirty-signal** (apply sets a per-endpoint `AtomicBool` / sends a signal like the existing `stop` flag; the brain re-reads+rebuilds manifest on its next pulse). `Arc<RwLock>` stays primary because the apply already holds the parsed new manifest (no brain-side re-resolution needed). If you switch, say so in the W3c commit with one line of why.\n...\n 31|- **§9 flag 5 (BINDING):** the translation restart (W3d step 5) MUST re-resolve the binary path from the **refreshed** manifest, not the cached `SpawnReq.translation_binary`, so an update that renames/moves the translation binary is picked up.\n*32|- **ADR-0025 amendment** — write it: add the **third axis** to the resident/ephemeral split (*ephemeral-but-install-dir-locking → run from a per-endpoint own copy*); correct the two errors (psyche-is-in-daemon; re-clone-into-running-BrainLifecycle); document broker-as-registry + manifest-refresh-via-shared-handle + the stop-path reap. Mark ADR-0025 status accepted (was proposed).\n 33|\n 34|## Build order — APPROVED, I gate incrementally\n 35|\n\n# .config/\n## nextest.toml\n 84|#\n*85|# REDISPATCH-TRUTH W1 (todlando 2026-07-16): redispatch is NEW — each test spawns\n 86|# two hermetic QUIC brokers + killable dispatch_fixture child processes (real\n 87|# dispatcher generations) — HEAVY AT BIRTH, same ruling. Added to all three\n 88|# <HEAVY> strings.\n 89|[[profile.default.overrides]]\n*90|filter = '(package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|conn_blackhole_lifecycle|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|mesh_recovery|net_worker_starve|redispatch|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|bind_honest_cross_perch_e2e|brain_respawn_rename|brain_split|brain_survive|daemon_refresh_e...\n 91|test-group = 'heavy-broker-pty'\n 92|\n 93|# FLAKE-LEDGER #14 (W-3, todlando 2026-07-06): the rc real-broker UNIT tests inside the\n\n# .github/workflows/\n## ci.yml\n 173|      - name: Test — Phase A (light pool, full parallel)\n*174|        run: cargo nextest run --workspace --no-fail-fast -E 'not ( (package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|conn_blackhole_lifecycle|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|mesh_recovery|net_worker_starve|redispatch|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|bind_honest_cross_perch_e2e|brain...\n 175|      - name: Test — Phase B (heavy class, serialized, on a now-quiet box)\n*176|        run: cargo nextest run --workspace --no-fail-fast -E '(package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|conn_blackhole_lifecycle|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|mesh_recovery|net_worker_starve|redispatch|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|bind_honest_cross_perch_e2e|brain_respa...\n 177|      # nextest does not run doctests — keep them on `cargo test --doc`.\n 178|      - name: Doctests\n 179|        run: cargo test --workspace --doc\n\n# crates/\n\n## spt-daemon/\n### Cargo.toml\n 7|\n*8|# Test-only REDISPATCH-TRUTH dispatcher-generation fixture for\n*9|# `tests/redispatch.rs` (REQ-HAZARD-REDISPATCH-CONTROL-STEAL /\n 10|# REQ-STREAM-OPENER-DURABLE / REQ-DISPATCH-CLAIM-RETRY): the REAL\n 11|# `run_dispatch_loop` as a KILLABLE child process — a killed generation drops\n 12|# its workers' broker conns exactly like a production brain death. Same\n\n### src/\n#### applyhost.rs\n 363|// [impl->REQ-DAEMON-REFRESH]\n*364|pub fn refresh_brain(broker_name: &str) -> Result<bool, String> {\n 365|    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\n 366|    brain\n 367|        .request_brain_restart()\n...\n 577|            .broker_image_version()\n*578|            .expect(\"broker image query round-trips\");\n 579|        assert_eq!(\n 580|            v.as_deref(),\n 581|            Some(env!(\"CARGO_PKG_VERSION\")),\n...\n 834|        assert!(\n*835|            !refresh_brain(&name).expect(\"refresh round-trips on a bare broker\"),\n 836|            \"no supervisor wired must report honored=false\"\n 837|        );\n 838|\n#### brain.rs\n 1445|\n*1446|    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A).\n 1447|    /// The dispatcher calls this when a serve loop runs to completion, so no\n 1448|    /// future dispatcher generation rediscovers and replays the stream\n 1449|    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\n...\n 1451|    /// broker to cycle onto the new binary).\n*1452|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1453|    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n 1454|        self.send(\n 1455|            KIND_NET_STREAM_RETIRE,\n#### brainproc.rs\n 61|const BRAIN_CONNECT_TRIES: u32 = 300;\n*62|/// Cadence the idle brain refreshes its ready breadcrumb + probes the broker.\n 63|const BRAIN_HEARTBEAT: Duration = Duration::from_millis(500);\n 64|\n 65|/// Why the broker spawned this brain — the Q4 update-vs-crash discriminator the\n#### broker.rs\n 4314|\n*4315|    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A):\n 4316|    /// the dispatcher reports a serve loop that ran to completion, and the row\n 4317|    /// leaves the table so no future dispatcher generation can rediscover and\n 4318|    /// replay it (the KNOWN-HAZARDS 7.41 control-steal class).\n*4319|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 4320|    fn dispatch_net_stream_retire(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4321|        let req: NetStreamRetireReq = serde_json::from_value(env.payload)\n 4322|            .map_err(|e| format!(\"bad net-stream-retire payload: {e}\"))?;\n#### dispatch.rs\n 233|/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n*234|/// redispatch — retire unserved, never replay?\n 235|///\n 236|/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\n 237|/// would be a regression: the request/reply families FIN their send WITH the\n...\n 250|/// else = a pending request — serve it.\n*251|// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n*252|// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\n 253|fn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n 254|    finished && family == StreamFamily::Attach\n 255|}\n...\n 343|                if matches!(outcome, DispatchOutcome::Served(_)) {\n*344|                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n 345|                    // this stream's exchange is over — take its row out of\n 346|                    // every FUTURE dispatcher generation's enumeration (the\n 347|                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n...\n 1002|\n*1003|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] the lifecycle+family\n 1004|    // discriminator (gate round 1): a finished ATTACH is terminal for\n*1005|    // redispatch (the operator detached — replay = the 7.41 controller\n 1006|    // steal), while a finished REQUEST/REPLY stream is NOT — sync and update\n 1007|    // requesters FIN their send WITH the request (sync.rs request_sync /\n 1008|    // propagate.rs fetch_update) and await the reply, so a bare finished\n#### endpoint.rs\n 76|/// supervised child process (ADR-0018 Q2/Q3 — the two-process restoration); the\n*77|/// child writes this once it has connected to the broker socket, and refreshes\n 78|/// it while attached. A test (or a future readiness-gated rollback, ADR-0018\n 79|/// Q7) reads it to confirm the brain came up and — on a pid change — that it\n 80|/// respawned. Best-effort like the daemon pid breadcrumb; liveness of the\n#### msg.rs\n 99|pub const KIND_BROKER_IMAGE: &str = \"broker-image\";\n*100|/// Broker→brain: the broker image version reply.\n 101|pub const KIND_BROKER_IMAGE_REPLY: &str = \"broker-image-reply\";\n 102|/// Brain→broker query: the brain-subscriber stall-evict tally (LIFECYCLE-TRUTH W2\n 103|/// observability rider, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) — how many\n...\n 152|/// the caller treats retire as best-effort).\n*153|// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 154|pub const KIND_NET_STREAM_RETIRE: &str = \"net-stream-retire\";\n 155|/// Broker→brain: the retire ack (`removed` = a row actually left the table).\n 156|pub const KIND_NET_STREAM_RETIRED: &str = \"net-stream-retired\";\n...\n 808|pub struct NetStreamRetireReq {\n*809|    /// The completed stream to retire from redispatch eligibility.\n 810|    pub stream_id: u64,\n 811|}\n 812|\n#### nethost.rs\n 544|    /// is load-bearing; physical teardown belongs to the conn-close sweep only.\n*545|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 546|    retired: std::sync::atomic::AtomicBool,\n 547|}\n 548|\n...\n 707|        // else reads a dead conn's stream ring.\n*708|        // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 709|        sh_close\n 710|            .streams\n 711|            .lock()\n...\n 1560|    /// call newly retired the row (idempotent; absent row = false).\n*1561|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1562|    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1563|        let Some(entry) = self\n 1564|            .shared\n...\n 2091|\n*2092|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the\n 2093|    // named row from dispatch eligibility: the retired stream leaves the\n 2094|    // dispatch enumeration (stream_infos) while the active sibling stays\n 2095|    // claimable, a second retire is a no-op, and the pinned-opener query\n...\n 2130|\n*2131|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement never severs the\n 2132|    // transport: a reply sent AFTER the row was retired still flushes to the\n 2133|    // peer. This is the field race's lost ordering made deterministic — the\n 2134|    // worker's reply send is fire-and-forget (brain returns before the broker\n...\n 2176|\n*2177|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] a dead CONNECTION retires its\n 2178|    // stream rows with it: the closed-watcher sweep leaves nothing for a\n 2179|    // future dispatcher generation to replay on a transport that can never\n 2180|    // serve again (the field bug's historical-rows-forever half).\n\n### tests/\n#### redispatch.rs\n*1|//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n 2|//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n 3|//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n 4|//! a dispatcher GENERATION whose death drops its workers' broker conns\n...\n 211|\n*212|// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n 213|// through PRODUCTION rediscovery: a finished historical Attach and a current\n 214|// active Attach on the same endpoint/origin; only the dispatcher generation\n 215|// restarts. The historical stream must neither take nor clear the current\n...\n 219|// and the retired row is GONE from the broker's enumeration\n*220|// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221|#[test]\n 222|fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223|    init_home();\n...\n 302|\n*303|// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\n 304|// finished-UNRETIRED orderings the Served-report path can never see: the\n 305|// pre-existing backlog and the brain-down-detach window. s1 is attached and\n 306|// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\n...\n 315|// ever being served (retire-on-sight drains the backlog).\n*316|// [int->REQ-REDISPATCH-FINISHED-RETIRE]\n 317|#[test]\n 318|fn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\n 319|    init_home();\n...\n 413|\n*414|// [int->REQ-REDISPATCH-FINISHED-RETIRE] the GUARD leg for the family-aware\n 415|// discriminator (gate round 1 counter-check): a request/reply stream reads\n 416|// `finished=true` the moment its requester FINs with the request — sync's\n 417|// `request_sync` does exactly that — so a finished row must NOT be blanket-\n\n#### fixtures/\n##### dispatch_fixture.rs\n*1|//! Test-only dispatcher-generation fixture (REDISPATCH-TRUTH D-regressions,\n*2|//! `tests/redispatch.rs`): runs the REAL `dispatch::run_dispatch_loop` against\n 3|//! the named broker, as a child process the test can KILL — a killed\n 4|//! generation drops its workers' broker connections exactly like a production\n 5|//! brain death, which no in-process thread can simulate (a parked serve-loop\n\n## spt/\n\n### src/\n#### cli.rs\n 4069|    }\n*4070|    match spt_daemon::refresh_brain(&spt_daemon::broker_socket_name()) {\n 4071|        Ok(true) => {\n 4072|            println!(\n 4073|                \"Daemon coordinator restarted in place — hosted sessions and connections were \\\n...\n 4213|    match running {\n*4214|        Some(v) if v == installed => format!(\"broker image: {v} (matches installed)\"),\n 4215|        Some(v) => format!(\n*4216|            \"broker image: {v} (running) — installed is {installed}; the daemon is still on the \\\n 4217|             previous version, so daemon-coordinated features run {v} until it restarts. Restart \\\n 4218|             with `spt daemon stop` then `spt daemon start`.\"\n 4219|        ),\n 4220|        None => format!(\n*4221|            \"broker image: not reported (the running daemon predates version reporting, or could \\\n 4222|             not be queried) — if it is older than the installed {installed}, restart with \\\n 4223|             `spt daemon stop` then `spt daemon start` to load {installed}.\"\n 4224|        ),\n\n### tests/\n#### daemon_refresh_e2e.rs\n 282|        broker_still_running,\n*283|        \"REQ-DAEMON-REFRESH: the broker process must survive the refresh untouched \\\n 284|         (pid {broker_pid}).\\n=== brain stderr ===\\n{brain_stderr}\"\n 285|    );\n 286|    assert!(\n\nShowing files 1-20 of 28. Use skip=20 for the next page, or narrow paths/pattern.\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":".","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":56,"fileCount":20,"files":[".config/nextest.toml",".github/workflows/ci.yml","CONTEXT.md","M12-W5-PLAN.md","W3-DESIGN-GATE.md","W3-DESIGN-PROPOSAL.md","W3-GATE-VERDICT.md","crates/spt-daemon/Cargo.toml","crates/spt-daemon/src/applyhost.rs","crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/brainproc.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/endpoint.rs","crates/spt-daemon/src/msg.rs","crates/spt-daemon/src/nethost.rs","crates/spt-daemon/tests/fixtures/dispatch_fixture.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt/src/cli.rs","crates/spt/tests/daemon_refresh_e2e.rs"],"fileMatches":[{"path":".config/nextest.toml","count":2},{"path":".github/workflows/ci.yml","count":2},{"path":"CONTEXT.md","count":3},{"path":"M12-W5-PLAN.md","count":3},{"path":"W3-DESIGN-GATE.md","count":1},{"path":"W3-DESIGN-PROPOSAL.md","count":4},{"path":"W3-GATE-VERDICT.md","count":2},{"path":"crates/spt-daemon/Cargo.toml","count":2},{"path":"crates/spt-daemon/src/applyhost.rs","count":3},{"path":"crates/spt-daemon/src/brain.rs","count":2},{"path":"crates/spt-daemon/src/brainproc.rs","count":1},{"path":"crates/spt-daemon/src/broker.rs","count":2},{"path":"crates/spt-daemon/src/dispatch.rs","count":6},{"path":"crates/spt-daemon/src/endpoint.rs","count":1},{"path":"crates/spt-daemon/src/msg.rs","count":3},{"path":"crates/spt-daemon/src/nethost.rs","count":6},{"path":"crates/spt-daemon/tests/fixtures/dispatch_fixture.rs","count":2},{"path":"crates/spt-daemon/tests/redispatch.rs","count":6},{"path":"crates/spt/src/cli.rs","count":4},{"path":"crates/spt/tests/daemon_refresh_e2e.rs","count":1}],"truncated":true,"fileLimitReached":20,"displayContent":"# CONTEXT.md\n 387│\n*388│**redispatch** (REDISPATCH-TRUTH, ADR-0038): the fresh brain's reconstruction of target-side attach workers over broker-held peer streams after a **brain cycle** (`spt daemon refresh` / `spt update` apply — the PTYs and QUIC streams survive; the brain-owned serve workers do not). Redispatch eligibility is **lifecycle-gated**: finished/terminal stream rows are retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach — the KNOWN-HAZARDS 7.41 frozen-PTY steal/clear...\n*389│_Avoid_: conflating redispatch with msg dispatch or session-cursor resume; filtering replay by origin identity (the legitimate same-`by` successor re-take must keep working — lifecycle is the discriminator).\n 390│\n 391│<!-- [doc->REQ-RC-KEY-VT-TRANSLATE] -->\n 392│**rc keyboard input (Windows VT translation, v0.13.0 bug 2).** On **Windows** an interactive `spt rc` console reads crossterm **key events** and translates each to **standard xterm VT** (`translate_key_event`) — arrows / Home / End / PgUp/Dn / Insert / Delete / F-keys + modifiers all reach the harness as the universal terminal contract (**agnostic**, NOT win32-input-mode; the legacy console delivers those keys as events, not bytes, so the old byte-pump left them DEAD). **Unix passes through** (its raw-...\n    │...\n 582│\n*583│**daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previously required a full daemon bounce. <!-- [doc->REQ-DAEMON-REFRESH] -->\n 584│\n 585│**project index** — a node's endpoint→project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers — list, picker, endpoint-info, hoo...\n 586│\n\n# M12-W5-PLAN.md\n 19│  first, then `SUBNET <name>` groups with endpoint rows. `--local` short-circuits to\n*20│  `cmd_list_local`. So \"alias for endpoint list\" is mostly a re-dispatch.\n 21│- **Self endpoint description** is reachable: `spt_store::info::read_info(&perch_path)\n 22│  .resources` where `perch_path = perch::resolve_perch_path(&id, ParentHint::Infer)` —\n 23│  exactly what `cmd_description` (cli.rs:1352, `DescriptionCmd::Show`) reads. `None` =\n   │...\n 31│## The DELTA\n*32│1. Re-dispatch `cmd_whoami` to the endpoint-list view (SELF pinned).\n 33│2. Augment the **SELF pin line** (in whoami, and arguably in `endpoint list` too) with the\n 34│   Self endpoint's authored description.\n 35│3. Reconcile the `how-to: ready` doc + register the REQ.\n   │...\n 69│  `info::read_info`. Pure-ish render unit-tested over a fixture roster row + blurb.\n*70│- **T5.1b** re-dispatch `cmd_whoami` to the chosen shape; reconcile the `how-to: ready`\n 71│  doc line + CHANGELOG.\n 72│- Sweep: full suite green · clippy -D · `traceable-reqs check` EXIT=0. W5 gate (doyle).\n 73│  Then **W2.5** (attach/kick) → full M12 gate → unblock perri.\n\n# W3-DESIGN-GATE.md\n 30│- **W3c manifest refresh.** A `BrainLifecycle` method (`lifecycle.rs:61` struct; `manifest: Manifest` field at `:70`, `runtime: ManifestRuntime` at `:62`, built in `with_config` `:93`/`:120-129`) that re-reads the new on-disk manifest and rebuilds BOTH `manifest` and `runtime` (`ManifestRuntime::with_install_dir`). Propose signature + call site.\n*31│- **W3d daemon-apply IPC.** CLI keeps fetch+verify (no new daemon HTTP). For an adapter with running resident children, CLI hands **apply** to the daemon over a new IPC command (where on the api/broker IPC surface?). Daemon apply **per affected endpoint**: stop resident children (translation; Psyche per (c)) → W3b swap → W3c refresh → restart resident (Psyche re-spawns from its new own-copy). Endpoint **not** running → CLI swaps directly (no lock, no cache, no round-trip). Route in `cmd_adapter_u...\n 32│- **Bounded + brain-parity + partial-failure (BINDING).** Stop→restart MUST be bounded (`join_bounded` / kill-bounded; **never ride a 240s backstop**). The endpoint (`BrainLifecycle`/broker) **never restarts** — only the resident child cycles. A mid-swap failure must **not strand** the endpoint (stopped binary + half-swapped files): restart-old / atomic-ish on failure — specify the recovery.\n 33│\n 34│## W3e — int keystone, NO mocks (the ship-blocker)\n\n# W3-DESIGN-PROPOSAL.md\n  16│\n *17│**Consequence:** W3a/W3d (cycle the translation child) belong in the **broker** and are clean. W3c (manifest refresh) **cannot** be \"re-clone into the running BrainLifecycle\" as ADR-0025 states — the live instance is thread-owned and IPC-unreachable. It needs a **shared refresh handle** the brain registers at host time. This is the biggest correction below.\n  18│\n  19│---\n  20│\n    │...\n 121│\n*122│- **ADR-0025 amendment** — add the **third axis** to the resident/ephemeral split: *ephemeral-but-install-dir-locking → run from a per-endpoint own copy*. Correct the two errors below; document (a)/(c)/broker-as-registry/manifest-refresh-via-shared-handle.\n 123│- **REQ-ADAPTER-LIVE-UPDATE** → `[\"doc\",\"impl\",\"unit\",\"int\"]` (activate at W3 build).\n 124│- **New `REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP`** (§4) — doyle confirms id/shape before mint.\n 125│- Tags `[impl/unit/int->REQ-ADAPTER-LIVE-UPDATE]` + `[unit/int->REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP]` on real evidence.\n    │...\n 140│\n*141│`W3a` (broker query + `HostedSession{adapter,install_dir}`) → `W3b` (pure `sha256`-CRC swap helper, unit-tested) → `W3c` (`BrainLifecycle` `Arc<RwLock>` + `refresh_manifest` + brain-registers-handle) → **(a)** stop-path scoped-reap + **(c)** psyche own-copy → `W3d` (`KIND_ADAPTER_APPLY` + `dispatch_adapter_apply` + CLI routing/decision) → `W3e` (NO-mocks int + Windows-lock regression).\n 142│\n*143│**No impl until doyle confirms** — especially: the §2 manifest-refresh mechanism, the §3 own-copy dir location, the §4 new REQ-HAZARD id/shape, and the §6 broker-IPC-vs-seedmap call.\n\n# W3-GATE-VERDICT.md\n  7│1. **W3a — reuse the broker `sessions` map as the resident registry** (no parallel structure). CONFIRMED. Add `HostedSession{adapter, install_dir}` + the 2 query helpers.\n *8│2. **W3c — manifest refresh via a shared `Arc<RwLock<Manifest>>` / `Arc<RwLock<ManifestRuntime>>` handle the brain registers at host into a broker-side per-endpoint map.** CONFIRMED as primary. Binding cautions:\n  9│   - **(i) Minimal read-lock scope** — every one of the 4 read sites must `read() → clone what it needs → drop the guard` BEFORE any spawn / blocking call / pulse-body work. A read guard held across `spawn_psyche`/`fire_echo` while an apply wants the write lock is the failure mode. Prove it (a quick comment at each site).\n 10│   - **(ii) Handle lifecycle** — the broker-side handle-map entry MUST be inserted at host and **removed on session/host teardown**, and **re-registered on brain restart** (the brain re-hosts; the Arc must not dangle or point at a dead brain's state). No stale handles.\n 11│   - **(iii) Fallback** — if the read-lock scoping across the 4 sites proves gnarly, the escape hatch is a brain-owned **reload-on-dirty-signal** (apply sets a per-endpoint `AtomicBool` / sends a signal like the existing `stop` flag; the brain re-reads+rebuilds manifest on its next pulse). `Arc<RwLock>` stays primary because the apply already holds the parsed new manifest (no brain-side re-resolution needed). If you switch, say so in the W3c commit with one line of why.\n   │...\n 31│- **§9 flag 5 (BINDING):** the translation restart (W3d step 5) MUST re-resolve the binary path from the **refreshed** manifest, not the cached `SpawnReq.translation_binary`, so an update that renames/moves the translation binary is picked up.\n*32│- **ADR-0025 amendment** — write it: add the **third axis** to the resident/ephemeral split (*ephemeral-but-install-dir-locking → run from a per-endpoint own copy*); correct the two errors (psyche-is-in-daemon; re-clone-into-running-BrainLifecycle); document broker-as-registry + manifest-refresh-via-shared-handle + the stop-path reap. Mark ADR-0025 status accepted (was proposed).\n 33│\n 34│## Build order — APPROVED, I gate incrementally\n 35│\n\n# .config/\n## nextest.toml\n 84│#\n*85│# REDISPATCH-TRUTH W1 (todlando 2026-07-16): redispatch is NEW — each test spawns\n 86│# two hermetic QUIC brokers + killable dispatch_fixture child processes (real\n 87│# dispatcher generations) — HEAVY AT BIRTH, same ruling. Added to all three\n 88│# <HEAVY> strings.\n 89│[[profile.default.overrides]]\n*90│filter = '(package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|conn_blackhole_lifecycle|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|mesh_recovery|net_worker_starve|redispatch|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|bind_honest_cross_perch_e2e|brain_respawn_rename|brain_split|brain_survive|daemon_refresh_e...\n 91│test-group = 'heavy-broker-pty'\n 92│\n 93│# FLAKE-LEDGER #14 (W-3, todlando 2026-07-06): the rc real-broker UNIT tests inside the\n\n# .github/workflows/\n## ci.yml\n 173│      - name: Test — Phase A (light pool, full parallel)\n*174│        run: cargo nextest run --workspace --no-fail-fast -E 'not ( (package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|conn_blackhole_lifecycle|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|mesh_recovery|net_worker_starve|redispatch|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|bind_honest_cross_perch_e2e|brain...\n 175│      - name: Test — Phase B (heavy class, serialized, on a now-quiet box)\n*176│        run: cargo nextest run --workspace --no-fail-fast -E '(package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|conn_blackhole_lifecycle|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|mesh_recovery|net_worker_starve|redispatch|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|bind_honest_cross_perch_e2e|brain_respa...\n 177│      # nextest does not run doctests — keep them on `cargo test --doc`.\n 178│      - name: Doctests\n 179│        run: cargo test --workspace --doc\n\n# crates/\n\n## spt-daemon/\n### Cargo.toml\n  7│\n *8│# Test-only REDISPATCH-TRUTH dispatcher-generation fixture for\n *9│# `tests/redispatch.rs` (REQ-HAZARD-REDISPATCH-CONTROL-STEAL /\n 10│# REQ-STREAM-OPENER-DURABLE / REQ-DISPATCH-CLAIM-RETRY): the REAL\n 11│# `run_dispatch_loop` as a KILLABLE child process — a killed generation drops\n 12│# its workers' broker conns exactly like a production brain death. Same\n\n### src/\n#### applyhost.rs\n 363│// [impl->REQ-DAEMON-REFRESH]\n*364│pub fn refresh_brain(broker_name: &str) -> Result<bool, String> {\n 365│    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\n 366│    brain\n 367│        .request_brain_restart()\n    │...\n 577│            .broker_image_version()\n*578│            .expect(\"broker image query round-trips\");\n 579│        assert_eq!(\n 580│            v.as_deref(),\n 581│            Some(env!(\"CARGO_PKG_VERSION\")),\n    │...\n 834│        assert!(\n*835│            !refresh_brain(&name).expect(\"refresh round-trips on a bare broker\"),\n 836│            \"no supervisor wired must report honored=false\"\n 837│        );\n 838│\n#### brain.rs\n 1445│\n*1446│    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A).\n 1447│    /// The dispatcher calls this when a serve loop runs to completion, so no\n 1448│    /// future dispatcher generation rediscovers and replays the stream\n 1449│    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\n     │...\n 1451│    /// broker to cycle onto the new binary).\n*1452│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1453│    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n 1454│        self.send(\n 1455│            KIND_NET_STREAM_RETIRE,\n#### brainproc.rs\n 61│const BRAIN_CONNECT_TRIES: u32 = 300;\n*62│/// Cadence the idle brain refreshes its ready breadcrumb + probes the broker.\n 63│const BRAIN_HEARTBEAT: Duration = Duration::from_millis(500);\n 64│\n 65│/// Why the broker spawned this brain — the Q4 update-vs-crash discriminator the\n#### broker.rs\n 4314│\n*4315│    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A):\n 4316│    /// the dispatcher reports a serve loop that ran to completion, and the row\n 4317│    /// leaves the table so no future dispatcher generation can rediscover and\n 4318│    /// replay it (the KNOWN-HAZARDS 7.41 control-steal class).\n*4319│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 4320│    fn dispatch_net_stream_retire(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4321│        let req: NetStreamRetireReq = serde_json::from_value(env.payload)\n 4322│            .map_err(|e| format!(\"bad net-stream-retire payload: {e}\"))?;\n#### dispatch.rs\n  233│/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n *234│/// redispatch — retire unserved, never replay?\n  235│///\n  236│/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\n  237│/// would be a regression: the request/reply families FIN their send WITH the\n     │...\n  250│/// else = a pending request — serve it.\n *251│// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n *252│// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\n  253│fn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n  254│    finished && family == StreamFamily::Attach\n  255│}\n     │...\n  343│                if matches!(outcome, DispatchOutcome::Served(_)) {\n *344│                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n  345│                    // this stream's exchange is over — take its row out of\n  346│                    // every FUTURE dispatcher generation's enumeration (the\n  347│                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n     │...\n 1002│\n*1003│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] the lifecycle+family\n 1004│    // discriminator (gate round 1): a finished ATTACH is terminal for\n*1005│    // redispatch (the operator detached — replay = the 7.41 controller\n 1006│    // steal), while a finished REQUEST/REPLY stream is NOT — sync and update\n 1007│    // requesters FIN their send WITH the request (sync.rs request_sync /\n 1008│    // propagate.rs fetch_update) and await the reply, so a bare finished\n#### endpoint.rs\n 76│/// supervised child process (ADR-0018 Q2/Q3 — the two-process restoration); the\n*77│/// child writes this once it has connected to the broker socket, and refreshes\n 78│/// it while attached. A test (or a future readiness-gated rollback, ADR-0018\n 79│/// Q7) reads it to confirm the brain came up and — on a pid change — that it\n 80│/// respawned. Best-effort like the daemon pid breadcrumb; liveness of the\n#### msg.rs\n  99│pub const KIND_BROKER_IMAGE: &str = \"broker-image\";\n*100│/// Broker→brain: the broker image version reply.\n 101│pub const KIND_BROKER_IMAGE_REPLY: &str = \"broker-image-reply\";\n 102│/// Brain→broker query: the brain-subscriber stall-evict tally (LIFECYCLE-TRUTH W2\n 103│/// observability rider, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) — how many\n    │...\n 152│/// the caller treats retire as best-effort).\n*153│// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 154│pub const KIND_NET_STREAM_RETIRE: &str = \"net-stream-retire\";\n 155│/// Broker→brain: the retire ack (`removed` = a row actually left the table).\n 156│pub const KIND_NET_STREAM_RETIRED: &str = \"net-stream-retired\";\n    │...\n 808│pub struct NetStreamRetireReq {\n*809│    /// The completed stream to retire from redispatch eligibility.\n 810│    pub stream_id: u64,\n 811│}\n 812│\n#### nethost.rs\n  544│    /// is load-bearing; physical teardown belongs to the conn-close sweep only.\n *545│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n  546│    retired: std::sync::atomic::AtomicBool,\n  547│}\n  548│\n     │...\n  707│        // else reads a dead conn's stream ring.\n *708│        // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n  709│        sh_close\n  710│            .streams\n  711│            .lock()\n     │...\n 1560│    /// call newly retired the row (idempotent; absent row = false).\n*1561│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1562│    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1563│        let Some(entry) = self\n 1564│            .shared\n     │...\n 2091│\n*2092│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the\n 2093│    // named row from dispatch eligibility: the retired stream leaves the\n 2094│    // dispatch enumeration (stream_infos) while the active sibling stays\n 2095│    // claimable, a second retire is a no-op, and the pinned-opener query\n     │...\n 2130│\n*2131│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement never severs the\n 2132│    // transport: a reply sent AFTER the row was retired still flushes to the\n 2133│    // peer. This is the field race's lost ordering made deterministic — the\n 2134│    // worker's reply send is fire-and-forget (brain returns before the broker\n     │...\n 2176│\n*2177│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] a dead CONNECTION retires its\n 2178│    // stream rows with it: the closed-watcher sweep leaves nothing for a\n 2179│    // future dispatcher generation to replay on a transport that can never\n 2180│    // serve again (the field bug's historical-rows-forever half).\n\n### tests/\n#### redispatch.rs\n  *1│//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n   2│//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n   3│//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n   4│//! a dispatcher GENERATION whose death drops its workers' broker conns\n    │...\n 211│\n*212│// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n 213│// through PRODUCTION rediscovery: a finished historical Attach and a current\n 214│// active Attach on the same endpoint/origin; only the dispatcher generation\n 215│// restarts. The historical stream must neither take nor clear the current\n    │...\n 219│// and the retired row is GONE from the broker's enumeration\n*220│// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221│#[test]\n 222│fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223│    init_home();\n    │...\n 302│\n*303│// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\n 304│// finished-UNRETIRED orderings the Served-report path can never see: the\n 305│// pre-existing backlog and the brain-down-detach window. s1 is attached and\n 306│// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\n    │...\n 315│// ever being served (retire-on-sight drains the backlog).\n*316│// [int->REQ-REDISPATCH-FINISHED-RETIRE]\n 317│#[test]\n 318│fn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\n 319│    init_home();\n    │...\n 413│\n*414│// [int->REQ-REDISPATCH-FINISHED-RETIRE] the GUARD leg for the family-aware\n 415│// discriminator (gate round 1 counter-check): a request/reply stream reads\n 416│// `finished=true` the moment its requester FINs with the request — sync's\n 417│// `request_sync` does exactly that — so a finished row must NOT be blanket-\n\n#### fixtures/\n##### dispatch_fixture.rs\n*1│//! Test-only dispatcher-generation fixture (REDISPATCH-TRUTH D-regressions,\n*2│//! `tests/redispatch.rs`): runs the REAL `dispatch::run_dispatch_loop` against\n 3│//! the named broker, as a child process the test can KILL — a killed\n 4│//! generation drops its workers' broker connections exactly like a production\n 5│//! brain death, which no in-process thread can simulate (a parked serve-loop\n\n## spt/\n\n### src/\n#### cli.rs\n 4069│    }\n*4070│    match spt_daemon::refresh_brain(&spt_daemon::broker_socket_name()) {\n 4071│        Ok(true) => {\n 4072│            println!(\n 4073│                \"Daemon coordinator restarted in place — hosted sessions and connections were \\\n     │...\n 4213│    match running {\n*4214│        Some(v) if v == installed => format!(\"broker image: {v} (matches installed)\"),\n 4215│        Some(v) => format!(\n*4216│            \"broker image: {v} (running) — installed is {installed}; the daemon is still on the \\\n 4217│             previous version, so daemon-coordinated features run {v} until it restarts. Restart \\\n 4218│             with `spt daemon stop` then `spt daemon start`.\"\n 4219│        ),\n 4220│        None => format!(\n*4221│            \"broker image: not reported (the running daemon predates version reporting, or could \\\n 4222│             not be queried) — if it is older than the installed {installed}, restart with \\\n 4223│             `spt daemon stop` then `spt daemon start` to load {installed}.\"\n 4224│        ),\n\n### tests/\n#### daemon_refresh_e2e.rs\n 282│        broker_still_running,\n*283│        \"REQ-DAEMON-REFRESH: the broker process must survive the refresh untouched \\\n 284│         (pid {broker_pid}).\\n=== brain stderr ===\\n{brain_stderr}\"\n 285│    );\n 286│    assert!(","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784247349471}}
{"type":"message","id":"caee89de","parentId":"8c32a4d3","timestamp":"2026-07-17T00:15:58.912Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Tracing release commit via git metadata**"},{"type":"toolCall","id":"call_XBX1lfacDn7OvRawHU8GXjps|fc_099a7567dadef391016a59743edf44819bb7f6cc9eb6cd5fa0","name":"read","arguments":{"i":"Reading release notes","path":"CHANGELOG.md","selector":"35-85"}},{"type":"toolCall","id":"call_YyUduCVcvq5LFcKQ7LxOQ1eJ|fc_099a7567dadef391016a59743edf54819b8e5a2f384f62ebc2","name":"read","arguments":{"i":"Reading redispatch ruling","path":"docs/REDISPATCH-TRUTH-TRIAGE.md","selector":"1-180"}},{"type":"toolCall","id":"call_4F15cm4q0iNjQ9d80pp2EOpW|fc_099a7567dadef391016a59743edf58819baad14d50cc1b777b","name":"read","arguments":{"i":"Reading lifecycle ADR","path":"docs/adr/0038-broker-stream-lifecycle-truth.md","selector":"1-260"}},{"type":"toolCall","id":"call_Bgl0tPLcI9HBeLQH7hbKS1l9|fc_099a7567dadef391016a59743edf60819ba93ba3389c85bf4a","name":"read","arguments":{"i":"Reading release workflow","path":"docs/RELEASE-RUNBOOK.md","selector":"1-110"}},{"type":"toolCall","id":"call_Z67AgtLhFMjr2EtMQkC5KxVc|fc_099a7567dadef391016a59743edf64819b9fc53da03ac5edab","name":"glob","arguments":{"i":"Finding requirement registry","path":"*req*; **/*req*; **/*REQ*; traceable-reqs.toml; Cargo.toml; crates/*/Cargo.toml; .git/refs/tags/*; .git/logs/refs/*/*; .git/packed-refs","hidden":true,"gitignore":false,"limit":300}},{"type":"toolCall","id":"call_3rVZTWYV8EWIWXwMjT6PMbJ2|fc_099a7567dadef391016a59743edf7c819b8361c1f001197211","name":"grep","arguments":{"i":"Finding release contract tags","pattern":"REQ-DAEMON-REFRESH|REQ-REDISPATCH|REQ-STREAM-OPENER-DURABLE|REQ-DISPATCH-CLAIM-RETRY|BROKER-VIEWER-BRAIN-DECOUPLE|0\\.34\\.0","path":"requirements; traceable-reqs.toml; docs; crates; CHANGELOG.md; Cargo.toml","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":23113,"output":455,"cacheRead":14848,"cacheWrite":0,"totalTokens":38416,"reasoningTokens":98,"cost":{"input":0.11556500000000001,"output":0.01365,"cacheRead":0.007424,"cacheWrite":0,"total":0.136639}},"stopReason":"toolUse","timestamp":1784247349516,"responseId":"resp_099a7567dadef391016a597435bf48819ba28073dcce8747db","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a597436abb8819ba6ff1e9becc0b23d","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXQ-LFYx36NCCMJT_4CWv04-UsCgJheeeHyBTCcOroflBdIavHZlIFDNrzmwft8wMnLceeUL_WnrmSB_aZtfQPede3JHO3CuZYWvlbrUd2c-Esp1aWjoyNnwQZvBYSCgyddIf_AyQhqe-_LBnTk4Puf49fsy2XRIZhAX9P5tEiYCFz0Y7VCSkUNte6fRDtkId_i2cy5EmlYfkHXYT-Goqs6KvWyUZ0W4yCDuAupk3PLLcVs_u5BDZHRsLteqVg2YsP-ne1857MRvUL5Sgj6h_g7PTLnZWJkY6KoEr_xXvv6VFaY9bMrAS4jCEAO5_C9IRMBBc4SS9FC3baYA_iSeGZFiHUeEmBzxPD3Qdkz21t18Ucpko284OUuvAjnnSmaMsTzENMvpbDdaM8nyd896ekypdV6pzbBkXiiseS3HSgqi1MJr1MYnCuZITSTJ6jKnJ_gzN_0LDelc1yoBBVOEjZDa-jLy7CiRA0l06Mt0n-0TtE7V2-ZeH-deAawpTVGu2wv4PPkhUkDbmEbwSr99p8JqNc7GfcCJUzsdntVpojDCL0BpFN4F4vSRRq9s6GrpPfqlQDtmKiTr9li5-s_PsrkMyzbSV1nfLRxHH8dtQ-RcFqC3YOjBC_6G1jQo4VuJysLEQBUxcRPXGcki6pEtd8U-i-Wf9xbOtIx-pdXqyHo1CGE0-Qpm-UtbdFJ8KX6Lq4Y21Aj1bcM2vynDaR47apYyWalH7k5Q4338LM4bfhgxvHyWf-b8kzNuYpQNwwuL0kDKCfnMIddDyqTFQkGgvfeRPxU_JD0X5mMHxzBM9nGa-eao-m0ASlj1pnR7nSqTEE--lPj8NqHRlkoH4ZOVQNNGhlG2WrODxPDhNOtnKUOh7UFaIfxVe3Y79JfyRts1zcOsyX7aY5vbHXs6EbQsGRQaX4oQsZQ2e1yHiQlcHEdxF_F9fmXflPTrftTCfFYgFyyuqtngSW1drJO3kurxFK5oVf8TCvXWdkI3MBk96uoXB8AtkvBoWWmUIS03IYELO-jSzm3jk9qnHQnEjxwdGbeERaXwQQgpCF6NBa4tcZc_cnRS0nLyW6YRpb8X8ZO_h1VPjN1zxlRM5sXJGtTjDDZDqo4PQ9glyV9XFwf8NB6HaV4iy_smY8D-IL4WTtOOWR_Nd3tXZo5iH6Jrjnsq_1vUHqv_68vR3Z40XZdamJFOzVZSmuWmgNiUpbwpWxeAr7ieep_TfmCOw1z9A3iw7LXcOFnI2UmvujdDSCma29iUEn3G7b2h-l0QJol4_Z2G1EU8HePfcarg6J3pOOuSHoaVqGoozjoa1hlJkJtfvWYyUTxhq6MOUgRix01WUX_E64fr1xnp2VIcMCwwZk0PGupxX9_F5mRCzFTnDv-BQ4_bhS-Xjp4z_5Oq3jS5ecQclqD9XQPJKqn341KINBP9FOCiuGUj1y7YgXlB11dew1Nah5OS2L639jXL_HKBQlYXTH7b9XFYd2n5ZBwllGF3DmjCJg==","summary":[{"type":"summary_text","text":"**Tracing release commit via git metadata**"}]},{"id":"fc_099a7567dadef391016a59743edf44819bb7f6cc9eb6cd5fa0","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading release notes\",\"path\":\"CHANGELOG.md\",\"selector\":\"35-85\"}","call_id":"call_XBX1lfacDn7OvRawHU8GXjps","name":"read"},{"id":"fc_099a7567dadef391016a59743edf54819b8e5a2f384f62ebc2","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading redispatch ruling\",\"path\":\"docs/REDISPATCH-TRUTH-TRIAGE.md\",\"selector\":\"1-180\"}","call_id":"call_YyUduCVcvq5LFcKQ7LxOQ1eJ","name":"read"},{"id":"fc_099a7567dadef391016a59743edf58819baad14d50cc1b777b","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading lifecycle ADR\",\"path\":\"docs/adr/0038-broker-stream-lifecycle-truth.md\",\"selector\":\"1-260\"}","call_id":"call_4F15cm4q0iNjQ9d80pp2EOpW","name":"read"},{"id":"fc_099a7567dadef391016a59743edf60819ba93ba3389c85bf4a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading release workflow\",\"path\":\"docs/RELEASE-RUNBOOK.md\",\"selector\":\"1-110\"}","call_id":"call_Bgl0tPLcI9HBeLQH7hbKS1l9","name":"read"},{"id":"fc_099a7567dadef391016a59743edf64819b9fc53da03ac5edab","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding requirement registry\",\"path\":\"*req*; **/*req*; **/*REQ*; traceable-reqs.toml; Cargo.toml; crates/*/Cargo.toml; .git/refs/tags/*; .git/logs/refs/*/*; .git/packed-refs\",\"hidden\":true,\"gitignore\":false,\"limit\":300}","call_id":"call_Z67AgtLhFMjr2EtMQkC5KxVc","name":"glob"},{"id":"fc_099a7567dadef391016a59743edf7c819b8361c1f001197211","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding release contract tags\",\"pattern\":\"REQ-DAEMON-REFRESH|REQ-REDISPATCH|REQ-STREAM-OPENER-DURABLE|REQ-DISPATCH-CLAIM-RETRY|BROKER-VIEWER-BRAIN-DECOUPLE|0\\\\.34\\\\.0\",\"path\":\"requirements; traceable-reqs.toml; docs; crates; CHANGELOG.md; Cargo.toml\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_3rVZTWYV8EWIWXwMjT6PMbJ2","name":"grep"}]},"duration":9387.817999996245,"ttft":1147.167199999094,"contextSnapshot":{"promptTokens":37961,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_XBX1lfacDn7OvRawHU8GXjps|fc_099a7567dadef391016a59743edf44819bb7f6cc9eb6cd5fa0","toolName":"read","startedAt":"2026-07-17T00:15:58.913Z","args":{"path":"CHANGELOG.md"},"intent":"Reading release notes"},"id":"3a9f7c4f","parentId":"caee89de","timestamp":"2026-07-17T00:15:58.913Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_YyUduCVcvq5LFcKQ7LxOQ1eJ|fc_099a7567dadef391016a59743edf54819b8e5a2f384f62ebc2","toolName":"read","startedAt":"2026-07-17T00:15:58.913Z","args":{"path":"docs/REDISPATCH-TRUTH-TRIAGE.md"},"intent":"Reading redispatch ruling"},"id":"89fedabb","parentId":"3a9f7c4f","timestamp":"2026-07-17T00:15:58.913Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_4F15cm4q0iNjQ9d80pp2EOpW|fc_099a7567dadef391016a59743edf58819baad14d50cc1b777b","toolName":"read","startedAt":"2026-07-17T00:15:58.913Z","args":{"path":"docs/adr/0038-broker-stream-lifecycle-truth.md"},"intent":"Reading lifecycle ADR"},"id":"312f036f","parentId":"89fedabb","timestamp":"2026-07-17T00:15:58.913Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Bgl0tPLcI9HBeLQH7hbKS1l9|fc_099a7567dadef391016a59743edf60819ba93ba3389c85bf4a","toolName":"read","startedAt":"2026-07-17T00:15:58.913Z","args":{"path":"docs/RELEASE-RUNBOOK.md"},"intent":"Reading release workflow"},"id":"8f8f341d","parentId":"312f036f","timestamp":"2026-07-17T00:15:58.913Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Z67AgtLhFMjr2EtMQkC5KxVc|fc_099a7567dadef391016a59743edf64819b9fc53da03ac5edab","toolName":"glob","startedAt":"2026-07-17T00:15:58.913Z","args":{"path":"*req*; **/*req*; **/*REQ*; traceable-reqs.toml; Cargo.toml; crates/*/Cargo.toml; .git/refs/tags/*; .git/logs/refs/*/*; .git/packed-refs"},"intent":"Finding requirement registry"},"id":"8cb13808","parentId":"8f8f341d","timestamp":"2026-07-17T00:15:58.913Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_3rVZTWYV8EWIWXwMjT6PMbJ2|fc_099a7567dadef391016a59743edf7c819b8361c1f001197211","toolName":"grep","startedAt":"2026-07-17T00:15:58.913Z","args":{"path":"requirements; traceable-reqs.toml; docs; crates; CHANGELOG.md; Cargo.toml"},"intent":"Finding release contract tags"},"id":"7ad9ec38","parentId":"8cb13808","timestamp":"2026-07-17T00:15:58.913Z"}
{"type":"message","id":"3b59afcd","parentId":"7ad9ec38","timestamp":"2026-07-17T00:15:58.935Z","message":{"role":"toolResult","toolCallId":"call_4F15cm4q0iNjQ9d80pp2EOpW|fc_099a7567dadef391016a59743edf58819baad14d50cc1b777b","toolName":"read","content":[{"type":"text","text":"# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)\n\n- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day)\n- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n\n## Context\n\nThe broker's `NetShared.streams` table is append-only in practice: rows are\ninserted once (nethost.rs ~649) and `StreamLog::finish` only marks EOF —\nno removal or retirement path exists. Stream *classification* is derived at\nredispatch time by peeking ring seq 0, but the `StreamLog` data ring is\nbounded (4096 transport chunks) and evicts the protocol opener under\ntraffic. Dispatcher claims are a per-process one-shot `HashSet` insert\nperformed before worker spawn, never cleared or retried.\n\nAfter any brain cycle (`spt daemon refresh`, `spt update` apply — same\npath), the fresh `dispatch::run_dispatch_loop` therefore replays EVERY\nbroker-held peer stream, including finished historical attach streams. A\nreplayed historical Attach silently steals the live controller\n(same-identity `become_controller`, no `Displaced`) and its replayed EOF\nclears it — the operator sees a frozen remote PTY (field: 4/5 endpoints,\ntwice on 2026-07-16). A high-traffic active stream whose opener evicted\nclassifies Unknown/Failed and is permanently abandoned (claim burned\npre-spawn).\n\n## Decision\n<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\n\nStream rows carry explicit **lifecycle truth**, and redispatch consumes it:\n\n1. **Finished is terminal for redispatch.** A finished/terminal stream row\n   is retired from redispatch eligibility (removed from the enumeration the\n   dispatcher claims from, or excluded by lifecycle state). Only the\n   post-EOF state genuinely needed by other readers (presence, log reads)\n   survives. A fresh dispatcher never re-serves a terminal Attach.\n   *Gate round 2 ruling: retirement is the EXCLUDED-BY-LIFECYCLE arm —\n   eligibility-visibility only, never transport teardown. Removing the row\n   races the worker's fire-and-forget reply send and resets the\n   never-finished send half (requester sees torn-EOF instead of its reply);\n   physical teardown belongs to the connection-close sweep alone.*\n2. **Classification identity is restart-durable and ring-independent.**\n   Each broker-held inbound stream pins an immutable bounded opener/\n   classification fact (the complete first NDJSON record or its derived\n   family+cursor facts) OUTSIDE the evictable data ring, held until stream\n   close. Recovery classifies from that metadata, never from ring seq 0.\n3. **Claims are retryable, outcomes are classified.** The dispatcher\n   distinguishes active/retryable vs served vs finished. A transient\n   worker-setup failure releases/requeues the claim; terminal outcomes do\n   not hot-loop.\n4. **Live reconstruction semantics are unchanged.** The legitimate\n   same-operator successor re-take after a brain restart (controller\n   identity keyed on `by`) still silently re-takes; the discriminator for\n   replay suppression is stream lifecycle, never origin identity.\n\n## Consequences\n\n- Brain cycles (refresh/update) stop being a controller-integrity hazard:\n  historical streams can neither take nor clear a live controller, and\n  active streams survive opener eviction — verified by production-path\n  regressions (the prior e2e manually re-served a stream and bypassed\n  rediscovery entirely).\n- The stream table gains a retirement duty (bounded growth instead of\n  forever-discoverable rows) and a small per-stream metadata cost (one\n  pinned opener record per stream, ring untouched).\n- Rejected alternatives: clearing the whole stream table on brain restart\n  (destroys live streams' reconstruction facts along with the stale ones);\n  enlarging the data ring to preserve openers (unbounded memory for a\n  correctness fact); filtering replay by origin identity (breaks the\n  legitimate successor re-take, the exact silent-steal seam); a\n  per-generation claimed-set handoff (patches the symptom, keeps terminal\n  rows eligible forever).\n"}],"details":{"displayContent":{"text":"# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)\r\n\r\n- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day)\r\n- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\r\n\r\n## Context\r\n\r\nThe broker's `NetShared.streams` table is append-only in practice: rows are\r\ninserted once (nethost.rs ~649) and `StreamLog::finish` only marks EOF —\r\nno removal or retirement path exists. Stream *classification* is derived at\r\nredispatch time by peeking ring seq 0, but the `StreamLog` data ring is\r\nbounded (4096 transport chunks) and evicts the protocol opener under\r\ntraffic. Dispatcher claims are a per-process one-shot `HashSet` insert\r\nperformed before worker spawn, never cleared or retried.\r\n\r\nAfter any brain cycle (`spt daemon refresh`, `spt update` apply — same\r\npath), the fresh `dispatch::run_dispatch_loop` therefore replays EVERY\r\nbroker-held peer stream, including finished historical attach streams. A\r\nreplayed historical Attach silently steals the live controller\r\n(same-identity `become_controller`, no `Displaced`) and its replayed EOF\r\nclears it — the operator sees a frozen remote PTY (field: 4/5 endpoints,\r\ntwice on 2026-07-16). A high-traffic active stream whose opener evicted\r\nclassifies Unknown/Failed and is permanently abandoned (claim burned\r\npre-spawn).\r\n\r\n## Decision\r\n<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\r\n\r\nStream rows carry explicit **lifecycle truth**, and redispatch consumes it:\r\n\r\n1. **Finished is terminal for redispatch.** A finished/terminal stream row\r\n   is retired from redispatch eligibility (removed from the enumeration the\r\n   dispatcher claims from, or excluded by lifecycle state). Only the\r\n   post-EOF state genuinely needed by other readers (presence, log reads)\r\n   survives. A fresh dispatcher never re-serves a terminal Attach.\r\n   *Gate round 2 ruling: retirement is the EXCLUDED-BY-LIFECYCLE arm —\r\n   eligibility-visibility only, never transport teardown. Removing the row\r\n   races the worker's fire-and-forget reply send and resets the\r\n   never-finished send half (requester sees torn-EOF instead of its reply);\r\n   physical teardown belongs to the connection-close sweep alone.*\r\n2. **Classification identity is restart-durable and ring-independent.**\r\n   Each broker-held inbound stream pins an immutable bounded opener/\r\n   classification fact (the complete first NDJSON record or its derived\r\n   family+cursor facts) OUTSIDE the evictable data ring, held until stream\r\n   close. Recovery classifies from that metadata, never from ring seq 0.\r\n3. **Claims are retryable, outcomes are classified.** The dispatcher\r\n   distinguishes active/retryable vs served vs finished. A transient\r\n   worker-setup failure releases/requeues the claim; terminal outcomes do\r\n   not hot-loop.\r\n4. **Live reconstruction semantics are unchanged.** The legitimate\r\n   same-operator successor re-take after a brain restart (controller\r\n   identity keyed on `by`) still silently re-takes; the discriminator for\r\n   replay suppression is stream lifecycle, never origin identity.\r\n\r\n## Consequences\r\n\r\n- Brain cycles (refresh/update) stop being a controller-integrity hazard:\r\n  historical streams can neither take nor clear a live controller, and\r\n  active streams survive opener eviction — verified by production-path\r\n  regressions (the prior e2e manually re-served a stream and bypassed\r\n  rediscovery entirely).\r\n- The stream table gains a retirement duty (bounded growth instead of\r\n  forever-discoverable rows) and a small per-stream metadata cost (one\r\n  pinned opener record per stream, ring untouched).\r\n- Rejected alternatives: clearing the whole stream table on brain restart\r\n  (destroys live streams' reconstruction facts along with the stale ones);\r\n  enlarging the data ring to preserve openers (unbounded memory for a\r\n  correctness fact); filtering replay by origin identity (breaks the\r\n  legitimate successor re-take, the exact silent-steal seam); a\r\n  per-generation claimed-set handoff (patches the symptom, keeps terminal\r\n  rows eligible forever).\r\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\adr\\0038-broker-stream-lifecycle-truth.md"}}},"isError":false,"timestamp":1784247358935}}
{"type":"message","id":"46f7734d","parentId":"3b59afcd","timestamp":"2026-07-17T00:15:58.935Z","message":{"role":"toolResult","toolCallId":"call_YyUduCVcvq5LFcKQ7LxOQ1eJ|fc_099a7567dadef391016a59743edf54819b8e5a2f384f62ebc2","toolName":"read","content":[{"type":"text","text":"# REDISPATCH-TRUTH — milestone triage (JIT)\n\n> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first\n> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:\n> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n> (hertz source-level, doyle seam-verified same day). Decision record:\n> ADR-0038. Lane: doyle triages → todlando builds → doyle gates → deployah\n> releases → hertz field-verify (they hold the incident logs).\n\n## Why (field: 4/5 endpoints frozen on one refresh, twice in one day)\n\n`spt daemon refresh` / `spt update` (same brain-cycle path, `applyhost.rs`)\nfreezes active remote `spt rc` sessions. The PTYs and broker-held QUIC\nstreams survive by construction — the freeze is a **production redispatch\nbug** in the fresh brain's stream reconstruction:\n\n1. **Stale replay steals live controllers.** The fresh\n   `dispatch::run_dispatch_loop` starts with an EMPTY per-process `claimed`\n   set (dispatch.rs:206) and its claim condition\n   (`initiated_locally || next_seq == 0 || !claimed.insert`, dispatch.rs:214)\n   has NO finished filter — while `NetShared.streams` has NO removal path\n   (single insert nethost.rs ~649; `StreamLog::finish` (:245) only marks).\n   So every dispatcher generation replays EVERY historical peer stream,\n   including finished attach streams. A replayed historical Attach calls\n   `attach_as(Control, same origin)` → same-identity silent\n   `become_controller` (no `Displaced`) steals the CURRENT controller;\n   the replayed EOF then `detach_session` CLEARS it. The live serve loop\n   still believes it is Controller but its sink is deselected → operator\n   sees a FROZEN PTY. Detach + fresh `spt rc` re-takes → explains the\n   no-PTY-restart recovery.\n2. **Opener eviction permanently abandons an active stream.** Reconstruction\n   classifies by `peek_first_line` (dispatch.rs:256) from ring seq 0, but\n   `StreamLog` is a bounded 4096-transport-chunk ring\n   (nethost.rs:111) — a high-traffic ACTIVE stream's Request record evicts\n   → replay classifies Unknown/Failed; `claimed.insert` happens PRE-spawn\n   and is never cleared/retried → that active stream is PERMANENTLY\n   abandoned.\n\nField evidence (HFENDULEAM, hertz preserves\n`AppData/Local/spt-core/logs/daemon.stderr.log{,.1}`, brain generations\n2/3): 4/5 endpoints frozen across both 2026-07-16 doyle refreshes\n(deployah-recovery + the v0.33.0 apply) + lia on the update;\n`DISPATCH:N:Failed` lines with low/historical stream ids in fresh\ngenerations = the indiscriminate-replay supporting evidence.\n\nWhy CI never caught it: `attach_survives_target_brain_restart_exactly_once`\n(crates/spt-daemon/tests/attach.rs:779) manually re-serves a known short\nstream — it BYPASSES production rediscovery/classification and never rolls\nthe opener.\n\n**Distinct from the CLOSED 0.30.x resume-steal saga** (brain session-cursor\nresume, fixed cursor-only in 0.30.5/6). This is the NET DISPATCHER leg —\ndo not re-anchor the old theories.\n\n## Shape (hertz A–D, doyle-endorsed; ADR-0038)\n\nBroker stream-table rows gain explicit **lifecycle truth**, and the\ndispatcher redispatches only what is genuinely live:\n\n- **A — finished rows retire from redispatch eligibility.** A fresh\n  dispatcher must never re-serve a terminal Attach request. Retain only the\n  lifecycle state genuinely needed after EOF (presence/log reads); the\n  redispatch enumeration must not see terminal rows.\n- **B — restart-durable opener/classification metadata.** Pin the\n  per-stream immutable opener fact (or the complete first NDJSON record)\n  OUTSIDE the evictable data ring, held until stream close. Recovery\n  classifies from that metadata, never from ring seq 0.\n- **C — retryable claims.** Distinguish active/retryable vs served vs\n  finished; a transient worker-setup failure requeues the claim; terminal\n  outcomes do NOT hot-loop.\n- **D — three production-path regressions** (all through the REAL\n  `run_dispatch_loop`, no manual re-serve):\n  1. finished historical Attach + current active Attach, same\n     endpoint/origin; restart target brain only → historical stream neither\n     takes nor clears the current controller; current input/output\n     exactly-once without detach;\n  2. >4096 transport chunks on an active Attach (opener evicted); restart\n     dispatcher → durable classification resumes the same operator stream;\n  3. one injected transient worker-start failure → claim retry recovers, no\n     duplicate controller/output.\n\n## Waves\n\n### W1 — the fix cluster (single build wave; todlando)\n- **REQ-HAZARD-REDISPATCH-CONTROL-STEAL** — the invariant (KNOWN-HAZARDS\n  7.41) + regression D1.\n- **REQ-REDISPATCH-FINISHED-RETIRE** — mechanism A.\n- **REQ-STREAM-OPENER-DURABLE** — mechanism B + regression D2.\n- **REQ-DISPATCH-CLAIM-RETRY** — mechanism C + regression D3.\n\nThen: doyle gates → deployah releases → hertz field-verify on HFENDULEAM\n(daemon refresh with active remote `spt rc` sessions: no frozen PTYs, no\nhistorical `DISPATCH:N:Failed` replay storm in the fresh generation).\n\n## Gate rulings (pre-made, binding at gate)\n\n1. **Production-path only.** Every D regression exercises the real\n   rediscovery path (`run_dispatch_loop` → peek/classify → serve). A test\n   that manually re-serves a known stream does not count as evidence (the\n   existing-e2e bypass is the lesson). The existing\n   `attach_survives_target_brain_restart_exactly_once` stays as-is but is\n   no longer the coverage for this class.\n2. **Successor re-take parity is binding.** The legitimate same-`by`\n   successor re-subscribe after a brain restart (CONTEXT.md controller\n   identity — silent re-take, `Displaced` only on genuine cross-operator\n   Take) MUST keep working. The fix removes STALE-replay takeover, not the\n   live reconstruction path. The discriminator is lifecycle (finished vs\n   active), never origin identity.\n3. **B must not grow the evictable ring.** Opener metadata is a bounded\n   per-stream side fact (one record), pinned until close; ring semantics\n   and `DEFAULT_STREAM_RING_CHUNKS` untouched.\n4. **C must not hot-loop.** Terminal classification outcomes are terminal;\n   retry is bounded/backoff-shaped for transient setup failures only —\n   show the distinction in the test, not just the code.\n5. **Every new e2e that spawns a daemon tree enters the HEAVY nextest group\n   AT BIRTH** (FLAKE-LEDGER #15; three identical strings: ci.yml Phase A +\n   Phase B + nextest.toml).\n6. Standard gate hygiene: isolated worktree-of-sha + fresh\n   `CARGO_TARGET_DIR`, nextest, workspace-wide clippy preflight,\n   `traceable-reqs check` green.\n\n## Possible kin (NOT folded — verify, don't assume)\n\n- deployah zombie controlled-by latch (REQ-ENDPOINT-CYCLE-HONEST seed,\n  daemon-lifecycle cluster): plausibly the same stale-stream fingerprint,\n  but unproven. The seed stands in its own cluster unless W1 evidence\n  shows the same root; do not widen this milestone on speculation.\n- stall-evict 15→17 baseline shift: likely same fingerprint; re-measure at\n  field-verify, no dedicated work.\n\n## Explicitly out of scope\n\n- MESH-RECOVERY (sibling milestone candidate, separate contract; next in\n  line).\n- Daemon-lifecycle cluster (endpoint-cycle-honest + comeback + massacre +\n  orphan-listener).\n- Any change to attach-intent semantics, controller/viewer model, or the\n  brain session-cursor resume path (0.30.5/6 fix stands).\n"}],"details":{"displayContent":{"text":"# REDISPATCH-TRUTH — milestone triage (JIT)\n\n> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first\n> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:\n> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n> (hertz source-level, doyle seam-verified same day). Decision record:\n> ADR-0038. Lane: doyle triages → todlando builds → doyle gates → deployah\n> releases → hertz field-verify (they hold the incident logs).\n\n## Why (field: 4/5 endpoints frozen on one refresh, twice in one day)\n\n`spt daemon refresh` / `spt update` (same brain-cycle path, `applyhost.rs`)\nfreezes active remote `spt rc` sessions. The PTYs and broker-held QUIC\nstreams survive by construction — the freeze is a **production redispatch\nbug** in the fresh brain's stream reconstruction:\n\n1. **Stale replay steals live controllers.** The fresh\n   `dispatch::run_dispatch_loop` starts with an EMPTY per-process `claimed`\n   set (dispatch.rs:206) and its claim condition\n   (`initiated_locally || next_seq == 0 || !claimed.insert`, dispatch.rs:214)\n   has NO finished filter — while `NetShared.streams` has NO removal path\n   (single insert nethost.rs ~649; `StreamLog::finish` (:245) only marks).\n   So every dispatcher generation replays EVERY historical peer stream,\n   including finished attach streams. A replayed historical Attach calls\n   `attach_as(Control, same origin)` → same-identity silent\n   `become_controller` (no `Displaced`) steals the CURRENT controller;\n   the replayed EOF then `detach_session` CLEARS it. The live serve loop\n   still believes it is Controller but its sink is deselected → operator\n   sees a FROZEN PTY. Detach + fresh `spt rc` re-takes → explains the\n   no-PTY-restart recovery.\n2. **Opener eviction permanently abandons an active stream.** Reconstruction\n   classifies by `peek_first_line` (dispatch.rs:256) from ring seq 0, but\n   `StreamLog` is a bounded 4096-transport-chunk ring\n   (nethost.rs:111) — a high-traffic ACTIVE stream's Request record evicts\n   → replay classifies Unknown/Failed; `claimed.insert` happens PRE-spawn\n   and is never cleared/retried → that active stream is PERMANENTLY\n   abandoned.\n\nField evidence (HFENDULEAM, hertz preserves\n`AppData/Local/spt-core/logs/daemon.stderr.log{,.1}`, brain generations\n2/3): 4/5 endpoints frozen across both 2026-07-16 doyle refreshes\n(deployah-recovery + the v0.33.0 apply) + lia on the update;\n`DISPATCH:N:Failed` lines with low/historical stream ids in fresh\ngenerations = the indiscriminate-replay supporting evidence.\n\nWhy CI never caught it: `attach_survives_target_brain_restart_exactly_once`\n(crates/spt-daemon/tests/attach.rs:779) manually re-serves a known short\nstream — it BYPASSES production rediscovery/classification and never rolls\nthe opener.\n\n**Distinct from the CLOSED 0.30.x resume-steal saga** (brain session-cursor\nresume, fixed cursor-only in 0.30.5/6). This is the NET DISPATCHER leg —\ndo not re-anchor the old theories.\n\n## Shape (hertz A–D, doyle-endorsed; ADR-0038)\n\nBroker stream-table rows gain explicit **lifecycle truth**, and the\ndispatcher redispatches only what is genuinely live:\n\n- **A — finished rows retire from redispatch eligibility.** A fresh\n  dispatcher must never re-serve a terminal Attach request. Retain only the\n  lifecycle state genuinely needed after EOF (presence/log reads); the\n  redispatch enumeration must not see terminal rows.\n- **B — restart-durable opener/classification metadata.** Pin the\n  per-stream immutable opener fact (or the complete first NDJSON record)\n  OUTSIDE the evictable data ring, held until stream close. Recovery\n  classifies from that metadata, never from ring seq 0.\n- **C — retryable claims.** Distinguish active/retryable vs served vs\n  finished; a transient worker-setup failure requeues the claim; terminal\n  outcomes do NOT hot-loop.\n- **D — three production-path regressions** (all through the REAL\n  `run_dispatch_loop`, no manual re-serve):\n  1. finished historical Attach + current active Attach, same\n     endpoint/origin; restart target brain only → historical stream neither\n     takes nor clears the current controller; current input/output\n     exactly-once without detach;\n  2. >4096 transport chunks on an active Attach (opener evicted); restart\n     dispatcher → durable classification resumes the same operator stream;\n  3. one injected transient worker-start failure → claim retry recovers, no\n     duplicate controller/output.\n\n## Waves\n\n### W1 — the fix cluster (single build wave; todlando)\n- **REQ-HAZARD-REDISPATCH-CONTROL-STEAL** — the invariant (KNOWN-HAZARDS\n  7.41) + regression D1.\n- **REQ-REDISPATCH-FINISHED-RETIRE** — mechanism A.\n- **REQ-STREAM-OPENER-DURABLE** — mechanism B + regression D2.\n- **REQ-DISPATCH-CLAIM-RETRY** — mechanism C + regression D3.\n\nThen: doyle gates → deployah releases → hertz field-verify on HFENDULEAM\n(daemon refresh with active remote `spt rc` sessions: no frozen PTYs, no\nhistorical `DISPATCH:N:Failed` replay storm in the fresh generation).\n\n## Gate rulings (pre-made, binding at gate)\n\n1. **Production-path only.** Every D regression exercises the real\n   rediscovery path (`run_dispatch_loop` → peek/classify → serve). A test\n   that manually re-serves a known stream does not count as evidence (the\n   existing-e2e bypass is the lesson). The existing\n   `attach_survives_target_brain_restart_exactly_once` stays as-is but is\n   no longer the coverage for this class.\n2. **Successor re-take parity is binding.** The legitimate same-`by`\n   successor re-subscribe after a brain restart (CONTEXT.md controller\n   identity — silent re-take, `Displaced` only on genuine cross-operator\n   Take) MUST keep working. The fix removes STALE-replay takeover, not the\n   live reconstruction path. The discriminator is lifecycle (finished vs\n   active), never origin identity.\n3. **B must not grow the evictable ring.** Opener metadata is a bounded\n   per-stream side fact (one record), pinned until close; ring semantics\n   and `DEFAULT_STREAM_RING_CHUNKS` untouched.\n4. **C must not hot-loop.** Terminal classification outcomes are terminal;\n   retry is bounded/backoff-shaped for transient setup failures only —\n   show the distinction in the test, not just the code.\n5. **Every new e2e that spawns a daemon tree enters the HEAVY nextest group\n   AT BIRTH** (FLAKE-LEDGER #15; three identical strings: ci.yml Phase A +\n   Phase B + nextest.toml).\n6. Standard gate hygiene: isolated worktree-of-sha + fresh\n   `CARGO_TARGET_DIR`, nextest, workspace-wide clippy preflight,\n   `traceable-reqs check` green.\n\n## Possible kin (NOT folded — verify, don't assume)\n\n- deployah zombie controlled-by latch (REQ-ENDPOINT-CYCLE-HONEST seed,\n  daemon-lifecycle cluster): plausibly the same stale-stream fingerprint,\n  but unproven. The seed stands in its own cluster unless W1 evidence\n  shows the same root; do not widen this milestone on speculation.\n- stall-evict 15→17 baseline shift: likely same fingerprint; re-measure at\n  field-verify, no dedicated work.\n\n## Explicitly out of scope\n\n- MESH-RECOVERY (sibling milestone candidate, separate contract; next in\n  line).\n- Daemon-lifecycle cluster (endpoint-cycle-honest + comeback + massacre +\n  orphan-listener).\n- Any change to attach-intent semantics, controller/viewer model, or the\n  brain session-cursor resume path (0.30.5/6 fix stands).\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\REDISPATCH-TRUTH-TRIAGE.md"}}},"isError":false,"timestamp":1784247358935}}
{"type":"message","id":"360da0f0","parentId":"46f7734d","timestamp":"2026-07-17T00:15:58.936Z","message":{"role":"toolResult","toolCallId":"call_Bgl0tPLcI9HBeLQH7hbKS1l9|fc_099a7567dadef391016a59743edf60819ba93ba3389c85bf4a","toolName":"read","content":[{"type":"text","text":"# Release runbook\n\n> M6-D6/D7 (ADR-0015). How a release ships. CI builds; the maintainer signs\n> locally — release keys never enter CI.\n\n<!-- [doc->REQ-REL-2] -->\n\n## One-time setup (done 2026-06-05)\n\n- Key ceremony: `cargo run -p xtask -- release-keygen rel-primary-2026` (and\n  `rel-recovery-2026`); both seeds in the password manager (+ separate paper\n  backup); both PUBLIC keys embedded in `BUILTIN_RELEASE_KEYS`\n  (`crates/spt-daemon/src/release.rs`). The recovery key is never used until\n  the primary is lost/compromised — then it signs the next release, which\n  rotates in a fresh primary.\n- `RELEASES_TOKEN` repo secret on spt-bs-core: fine-grained PAT, Contents\n  read/write on `BigscreenVR/spt-bs-releases` only.\n\n## Per release\n\n1. **Bump inside the release PR — never as a separate push to `main`.** Put the\n   version bump (`[workspace.package] version` in root `Cargo.toml` **and** the\n   matching first-party `spt-*` lines in `Cargo.lock` — leave third-party lines\n   alone), the regenerated docs (`cargo run -p xtask -- gen`), and the changelog\n   section (step 2) **in one PR**: the feature PR's final commit, or a dedicated\n   release PR when the work already merged. That PR's CI validates the **exact\n   commit that gets tagged**, so on merge you tag straight away — the green PR run\n   is the gate.\n   - **Do not** author the bump as a direct push to `main` and then wait for a\n     second full `push:[main]` CI pass before tagging. The code is byte-identical\n     to what the feature PR already proved green (only the version string,\n     changelog, and lockfile lines differ); that second pass is ~one CI cycle of\n     pure latency.\n   - Merging the release PR still fires `push:[main]` CI automatically — let it\n     run as a **non-blocking backstop**; it is **not** a tag gate. Tag the merged\n     `main` HEAD as soon as the PR is merged.\n2. **Write the user-facing changelog** — add a `## [0.X.Y] - <date>` section at\n   the top of `CHANGELOG.md` (under the intro) with **Added / Changed / Fixed**\n   subsections. This section becomes the GitHub Release **body** verbatim\n   (`release.yml` extracts it via `--notes-file`), so it is the changelog every\n   spt user reads. Rules:\n   - **spt-user-facing UX only.** What a person running the `spt` CLI notices or\n     does differently. Name the actual commands/flags they type.\n   - **No internal lingo** — no requirement ids, crate names, commit hashes,\n     milestone/hazard codes, or under-the-hood mechanics. A reader who has never\n     seen the source must understand every line.\n   - **Flag breaking changes** prominently under Changed.\n   - The release **fails loudly** if the tagged version has no `## [0.X.Y]`\n     section — the changelog is not optional.\n   - **After inserting the new section, assert the version ladder is\n     contiguous**: `grep '^## \\[' CHANGELOG.md` and check the versions descend\n     with no gap. Inserting the new heading with an editor whose anchor spans the\n     previous heading can silently *consume* that heading (v0.34.0 did this to\n     `## [0.33.0]`), folding the prior release's body into the new section — and\n     since the body is published verbatim, the release notes then carry two\n     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n3. **Tag**: `git tag v0.X.Y && git push origin v0.X.Y`. This triggers:\n   - `release.yml` — both runners build, the assemble job extracts this\n     version's `CHANGELOG.md` section into the release body and creates a\n     **draft** release on spt-releases with `spt-x86_64-linux`,\n     `spt-x86_64-windows.exe`, `SHA256SUMS`, `manifest.schema.json`,\n     `mock-adapter.zip`;\n   - `docs-publish.yml` — docs tree + repo-face sync to spt-releases.\n4. **Sign + publish** (local, one command):\n\n   ```sh\n   SPT_RELEASE_SEED_CMD='<your password-manager CLI read command>' \\\n     cargo run -p xtask -- release-publish \\\n       --tag v0.X.Y --key-id rel-primary-2026 --version <N>\n   ```\n\n   It downloads the draft's binaries, **verifies them against SHA256SUMS\n   before signing**, signs each (`*.release.json` in the exact\n   `SignedRelease` shape `spt update` verifies), uploads the metadata\n   assets, and flips the draft public. `--version <N>` is the **monotonic\n   release counter** (u64; strictly increasing across releases — it is not\n   the semver). `SPT_RELEASE_SEED` (raw hex) works instead of\n   `_CMD` when pasting from the manager by hand.\n\n## Notes\n\n- The wire-protocol version (REQ-ARCH-3) and the release counter and the\n  workspace semver are three independent numbers. Never conflate.\n- The installer scripts trust HTTPS + `SHA256SUMS` on first fetch; the\n  `*.release.json` metadata is what `spt update` verifies thereafter against\n  the embedded two-key anchor.\n- Revoking a leaked key without a rebuild: add its id to `\"revoked\"` in\n  `identity/release-keys.json` on each node (the file overlays the builtin\n  set); ship the next release signed by the other key.\n"}],"details":{"displayContent":{"text":"# Release runbook\r\n\r\n> M6-D6/D7 (ADR-0015). How a release ships. CI builds; the maintainer signs\r\n> locally — release keys never enter CI.\r\n\r\n<!-- [doc->REQ-REL-2] -->\r\n\r\n## One-time setup (done 2026-06-05)\r\n\r\n- Key ceremony: `cargo run -p xtask -- release-keygen rel-primary-2026` (and\r\n  `rel-recovery-2026`); both seeds in the password manager (+ separate paper\r\n  backup); both PUBLIC keys embedded in `BUILTIN_RELEASE_KEYS`\r\n  (`crates/spt-daemon/src/release.rs`). The recovery key is never used until\r\n  the primary is lost/compromised — then it signs the next release, which\r\n  rotates in a fresh primary.\r\n- `RELEASES_TOKEN` repo secret on spt-bs-core: fine-grained PAT, Contents\r\n  read/write on `BigscreenVR/spt-bs-releases` only.\r\n\r\n## Per release\r\n\r\n1. **Bump inside the release PR — never as a separate push to `main`.** Put the\r\n   version bump (`[workspace.package] version` in root `Cargo.toml` **and** the\r\n   matching first-party `spt-*` lines in `Cargo.lock` — leave third-party lines\r\n   alone), the regenerated docs (`cargo run -p xtask -- gen`), and the changelog\r\n   section (step 2) **in one PR**: the feature PR's final commit, or a dedicated\r\n   release PR when the work already merged. That PR's CI validates the **exact\r\n   commit that gets tagged**, so on merge you tag straight away — the green PR run\r\n   is the gate.\r\n   - **Do not** author the bump as a direct push to `main` and then wait for a\r\n     second full `push:[main]` CI pass before tagging. The code is byte-identical\r\n     to what the feature PR already proved green (only the version string,\r\n     changelog, and lockfile lines differ); that second pass is ~one CI cycle of\r\n     pure latency.\r\n   - Merging the release PR still fires `push:[main]` CI automatically — let it\r\n     run as a **non-blocking backstop**; it is **not** a tag gate. Tag the merged\r\n     `main` HEAD as soon as the PR is merged.\r\n2. **Write the user-facing changelog** — add a `## [0.X.Y] - <date>` section at\r\n   the top of `CHANGELOG.md` (under the intro) with **Added / Changed / Fixed**\r\n   subsections. This section becomes the GitHub Release **body** verbatim\r\n   (`release.yml` extracts it via `--notes-file`), so it is the changelog every\r\n   spt user reads. Rules:\r\n   - **spt-user-facing UX only.** What a person running the `spt` CLI notices or\r\n     does differently. Name the actual commands/flags they type.\r\n   - **No internal lingo** — no requirement ids, crate names, commit hashes,\r\n     milestone/hazard codes, or under-the-hood mechanics. A reader who has never\r\n     seen the source must understand every line.\r\n   - **Flag breaking changes** prominently under Changed.\r\n   - The release **fails loudly** if the tagged version has no `## [0.X.Y]`\r\n     section — the changelog is not optional.\r\n   - **After inserting the new section, assert the version ladder is\r\n     contiguous**: `grep '^## \\[' CHANGELOG.md` and check the versions descend\r\n     with no gap. Inserting the new heading with an editor whose anchor spans the\r\n     previous heading can silently *consume* that heading (v0.34.0 did this to\r\n     `## [0.33.0]`), folding the prior release's body into the new section — and\r\n     since the body is published verbatim, the release notes then carry two\r\n     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\r\n3. **Tag**: `git tag v0.X.Y && git push origin v0.X.Y`. This triggers:\r\n   - `release.yml` — both runners build, the assemble job extracts this\r\n     version's `CHANGELOG.md` section into the release body and creates a\r\n     **draft** release on spt-releases with `spt-x86_64-linux`,\r\n     `spt-x86_64-windows.exe`, `SHA256SUMS`, `manifest.schema.json`,\r\n     `mock-adapter.zip`;\r\n   - `docs-publish.yml` — docs tree + repo-face sync to spt-releases.\r\n4. **Sign + publish** (local, one command):\r\n\r\n   ```sh\r\n   SPT_RELEASE_SEED_CMD='<your password-manager CLI read command>' \\\r\n     cargo run -p xtask -- release-publish \\\r\n       --tag v0.X.Y --key-id rel-primary-2026 --version <N>\r\n   ```\r\n\r\n   It downloads the draft's binaries, **verifies them against SHA256SUMS\r\n   before signing**, signs each (`*.release.json` in the exact\r\n   `SignedRelease` shape `spt update` verifies), uploads the metadata\r\n   assets, and flips the draft public. `--version <N>` is the **monotonic\r\n   release counter** (u64; strictly increasing across releases — it is not\r\n   the semver). `SPT_RELEASE_SEED` (raw hex) works instead of\r\n   `_CMD` when pasting from the manager by hand.\r\n\r\n## Notes\r\n\r\n- The wire-protocol version (REQ-ARCH-3) and the release counter and the\r\n  workspace semver are three independent numbers. Never conflate.\r\n- The installer scripts trust HTTPS + `SHA256SUMS` on first fetch; the\r\n  `*.release.json` metadata is what `spt update` verifies thereafter against\r\n  the embedded two-key anchor.\r\n- Revoking a leaked key without a rebuild: add its id to `\"revoked\"` in\r\n  `identity/release-keys.json` on each node (the file overlays the builtin\r\n  set); ship the next release signed by the other key.\r\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\RELEASE-RUNBOOK.md"}}},"isError":false,"timestamp":1784247358936}}
{"type":"message","id":"9f705d18","parentId":"360da0f0","timestamp":"2026-07-17T00:15:58.958Z","message":{"role":"toolResult","toolCallId":"call_XBX1lfacDn7OvRawHU8GXjps|fc_099a7567dadef391016a59743edf44819bb7f6cc9eb6cd5fa0","toolName":"read","content":[{"type":"text","text":"## [0.35.0] - 2026-07-16\n…\n### Fixed\n…\n- **A node no longer strands itself from its subnets after a brief failure to\n…\n  Routes are now kept and marked unreachable rather than deleted, and address\n  resolution falls back to the roster, so the node recovers on its own with no\n  manual state surgery.\n\n- **Poisoned peer-address entries are repaired at startup.** If a cached peer\n  address has come to claim a different peer's identity, the daemon now detects\n  and repairs it from the subnet roster when it starts — and says so loudly in\n  the log — rather than carrying the bad entry forward.\n\n## [0.34.0] - 2026-07-16\n\nA stability fix for live remote sessions during a daemon update.\n\n### Fixed\n\n- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\n  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\n  A daemon cycle could previously replay already-finished output onto a live\n  remote terminal: old text re-typed itself, control of the session was stolen,\n  or a running session was left frozen. The daemon now treats a finished\n  session's history as terminal and never re-drives it onto a live terminal, so\n  the remote sessions you are attached to keep running cleanly across an update.\n\n## [0.33.0] - 2026-07-16\n\nA performance and identity release. The commands that list endpoints and choose\na run now answer in a fraction of a second instead of many seconds, `spt whoami`\nbecomes a focused identity command, and the daemon gains a health report for the\nindex that makes the fast listings possible.\n\n### Changed\n\n- **Breaking: `spt whoami` reports only your own identity.** It now prints the\n  single endpoint bound to the current session, not the full roster of endpoints\n  on the node — use `spt endpoint list` for the roster. Its `--json` output is a\n  new, stable object — `{id, state?, ready?, alive?, unbound?, description?}` —\n  rather than the list shape it returned before. When the current session is not\n  bound to any endpoint, it prints `NO_PERCH` on stderr (JSON: `{\"id\": null}`)\n  and exits non-zero, where it used to succeed. The command runs in bounded time\n  and is safe to call from shell hooks and prompts.\n\n- **Listing endpoints is now fast.** `spt endpoint list` (both the human table\n  and `--json`), the interactive run picker, and `spt api endpoint-info` read a\n  project index the daemon keeps current, instead of inspecting each project's\n  git state on every call. On a node with many endpoints this takes these\n  commands from many seconds to well under a second. Two notes on the trade-off:\n  a project you just changed may show its previous attribution for a brief moment\n  (the index refreshes on change and reconciles periodically), and on a node\n  whose daemon has not yet built the index, attribution shows as `-` until the\n  first build lands.\n\n### Added\n\n- `spt daemon status` now reports the health of the project index that powers the\n  fast listings — when it was last generated, how many projects it covers, the\n…\n  health — the status report is.\n\n…\n\n\n[Showing lines 34-88 of 1126. Use :89 to continue]"}],"details":{"truncation":{"content":"  Routes are now kept and marked unreachable rather than deleted, and address\r\n  resolution falls back to the roster, so the node recovers on its own with no\r\n  manual state surgery.\r\n\r\n- **Poisoned peer-address entries are repaired at startup.** If a cached peer\r\n  address has come to claim a different peer's identity, the daemon now detects\r\n  and repairs it from the subnet roster when it starts — and says so loudly in\r\n  the log — rather than carrying the bad entry forward.\r\n\r\n## [0.34.0] - 2026-07-16\r\n\r\nA stability fix for live remote sessions during a daemon update.\r\n\r\n### Fixed\r\n\r\n- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\r\n  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\r\n  A daemon cycle could previously replay already-finished output onto a live\r\n  remote terminal: old text re-typed itself, control of the session was stolen,\r\n  or a running session was left frozen. The daemon now treats a finished\r\n  session's history as terminal and never re-drives it onto a live terminal, so\r\n  the remote sessions you are attached to keep running cleanly across an update.\r\n\r\n## [0.33.0] - 2026-07-16\r\n\r\nA performance and identity release. The commands that list endpoints and choose\r\na run now answer in a fraction of a second instead of many seconds, `spt whoami`\r\nbecomes a focused identity command, and the daemon gains a health report for the\r\nindex that makes the fast listings possible.\r\n\r\n### Changed\r\n\r\n- **Breaking: `spt whoami` reports only your own identity.** It now prints the\r\n  single endpoint bound to the current session, not the full roster of endpoints\r\n  on the node — use `spt endpoint list` for the roster. Its `--json` output is a\r\n  new, stable object — `{id, state?, ready?, alive?, unbound?, description?}` —\r\n  rather than the list shape it returned before. When the current session is not\r\n  bound to any endpoint, it prints `NO_PERCH` on stderr (JSON: `{\"id\": null}`)\r\n  and exits non-zero, where it used to succeed. The command runs in bounded time\r\n  and is safe to call from shell hooks and prompts.\r\n\r\n- **Listing endpoints is now fast.** `spt endpoint list` (both the human table\r\n  and `--json`), the interactive run picker, and `spt api endpoint-info` read a\r\n  project index the daemon keeps current, instead of inspecting each project's\r\n  git state on every call. On a node with many endpoints this takes these\r\n  commands from many seconds to well under a second. Two notes on the trade-off:\r\n  a project you just changed may show its previous attribution for a brief moment\r\n  (the index refreshes on change and reconciles periodically), and on a node\r\n  whose daemon has not yet built the index, attribution shows as `-` until the\r\n  first build lands.\r\n\r\n### Added\r\n\r\n- `spt daemon status` now reports the health of the project index that powers the\r\n  fast listings — when it was last generated, how many projects it covers, the\r","truncated":true,"truncatedBy":"lines","totalLines":1093,"totalBytes":2969,"outputLines":55,"outputBytes":2969,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"## [0.35.0] - 2026-07-16\n…\n### Fixed\n…\n- **A node no longer strands itself from its subnets after a brief failure to\n…\n  Routes are now kept and marked unreachable rather than deleted, and address\r\n  resolution falls back to the roster, so the node recovers on its own with no\r\n  manual state surgery.\r\n\r\n- **Poisoned peer-address entries are repaired at startup.** If a cached peer\r\n  address has come to claim a different peer's identity, the daemon now detects\r\n  and repairs it from the subnet roster when it starts — and says so loudly in\r\n  the log — rather than carrying the bad entry forward.\r\n\r\n## [0.34.0] - 2026-07-16\r\n\r\nA stability fix for live remote sessions during a daemon update.\r\n\r\n### Fixed\r\n\r\n- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\r\n  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\r\n  A daemon cycle could previously replay already-finished output onto a live\r\n  remote terminal: old text re-typed itself, control of the session was stolen,\r\n  or a running session was left frozen. The daemon now treats a finished\r\n  session's history as terminal and never re-drives it onto a live terminal, so\r\n  the remote sessions you are attached to keep running cleanly across an update.\r\n\r\n## [0.33.0] - 2026-07-16\r\n\r\nA performance and identity release. The commands that list endpoints and choose\r\na run now answer in a fraction of a second instead of many seconds, `spt whoami`\r\nbecomes a focused identity command, and the daemon gains a health report for the\r\nindex that makes the fast listings possible.\r\n\r\n### Changed\r\n\r\n- **Breaking: `spt whoami` reports only your own identity.** It now prints the\r\n  single endpoint bound to the current session, not the full roster of endpoints\r\n  on the node — use `spt endpoint list` for the roster. Its `--json` output is a\r\n  new, stable object — `{id, state?, ready?, alive?, unbound?, description?}` —\r\n  rather than the list shape it returned before. When the current session is not\r\n  bound to any endpoint, it prints `NO_PERCH` on stderr (JSON: `{\"id\": null}`)\r\n  and exits non-zero, where it used to succeed. The command runs in bounded time\r\n  and is safe to call from shell hooks and prompts.\r\n\r\n- **Listing endpoints is now fast.** `spt endpoint list` (both the human table\r\n  and `--json`), the interactive run picker, and `spt api endpoint-info` read a\r\n  project index the daemon keeps current, instead of inspecting each project's\r\n  git state on every call. On a node with many endpoints this takes these\r\n  commands from many seconds to well under a second. Two notes on the trade-off:\r\n  a project you just changed may show its previous attribution for a brief moment\r\n  (the index refreshes on change and reconciles periodically), and on a node\r\n  whose daemon has not yet built the index, attribution shows as `-` until the\r\n  first build lands.\r\n\r\n### Added\r\n\r\n- `spt daemon status` now reports the health of the project index that powers the\r\n  fast listings — when it was last generated, how many projects it covers, the\r\n…\n  health — the status report is.\n\n…\n","startLine":12,"lineNumbers":[12,null,28,null,30,null,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,null,93,94,null,106]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\CHANGELOG.md"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1126,"totalBytes":2969,"outputLines":55,"outputBytes":2969,"shownRange":{"start":34,"end":88},"nextOffset":89}}},"isError":false,"timestamp":1784247358958}}
{"type":"message","id":"7545c465","parentId":"9f705d18","timestamp":"2026-07-17T00:15:58.982Z","message":{"role":"toolResult","toolCallId":"call_3rVZTWYV8EWIWXwMjT6PMbJ2|fc_099a7567dadef391016a59743edf7c819b8361c1f001197211","toolName":"grep","content":[{"type":"text","text":"# traceable-reqs.toml\n 2109|[[requirements]]\n*2110|id = \"REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE\"\n 2111|title = \"W2 (LIFECYCLE-TRUTH, KNOWN-HAZARDS, flagship — the update wedge): PTY viewer fan-out and control mutations must not depend synchronously on a live draining brain. ROOT rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): brain-subscriber session-output writes ride UNDER the per-session log lock (broker.rs:19-20); failed writes are handled (cursor freeze + detach :3486) but BLOCKED writes are not. Suspended brain => within seconds attached rc output freezes; detach does NOT releas...\n 2112|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED LIFECYCLE-TRUTH W2 (todlando 2026-07-07) per doyle ruling docs/W2-DESIGN-RULING.md @60ec0ed (minimal-plus scope). doc = KNOWN-HAZARDS entry (\"PTY fan-out + broker control plane never block on any single subscriber conn — bounded sinks everywhere incl. the brain conn; brain.ready != subscribers drained\"). impl = (a) bounded brain-conn sink (writer thread + bounded queue + BRAIN_WRITE_DEADLINE=15s deadline-evict, full-with-zero-progress last_o...\n 2113|\n...\n 2140|id = \"REQ-UPDATE-PROMOTE-DRAINED\"\n*2141|title = \"W3 (LIFECYCLE-TRUTH, mechanic-d MOVED FROM W2 per doyle gate verdict @e5ae7a9 — binding): the update-apply brain-generation promotion completes only when the OLD generation's broker subscriber connection is CLOSED or stall-EVICTED — never while blocked writes still pend on it. ROOT: `brain.ready` != subscribers drained; W2's stall-evict (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) only BOUNDS the false-promote window to BRAIN_WRITE_DEADLINE (15s), it does NOT close it — a new brain can signal...\n 2142|required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED LIFECYCLE-TRUTH W3 drained-condition slice (todlando 2026-07-07, work-start); brainproc.rs run_trial DRAINED gate + broker any_local_controller_wedged truth + false-promote int rig — closes the W2 15s residual (KH 7.36)\n 2143|\n 2144|[[requirements]]\n 2145|id = \"REQ-UPDATE-TRIAL-DRAIN-DRIVE\"\n*2146|title = \"UPDATE-WEDGE (counter-54, doyle-ruled 2026-07-09 — regression of the v0.29.0 seamless brain-swap): a brain generation DRIVES the broker's controller-liveness reap (a KIND_SESSIONS poll) each heartbeat throughout its boot/trial loop, so a hard-KILLED prior generation's black-holed LOCAL controller conn (by:None) is stall-evicted within the trial window and can never permanently strand the promotion DRAINED gate. ROOT (2026-07-09 field freeze, `spt update fetch --apply` v0.30.0->v0.30.2 froze al...\n 2147|required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED counter-54 UPDATE-WEDGE fix (doyle ruled brain-side 2026-07-09; todlando builds). doc = a CONTEXT.md/LIFECYCLE note that the trial/boot brain drives controller-reap so a killed prior generation cannot strand old_gen_drained. impl = the brain.sessions() KIND_SESSIONS drive in run_brain's heartbeat loop (brainproc.rs, first-tick + every tick, trial + recovery brain). UNIT DELIBERATELY DROPPED (doyle FORK-2 ruling 2026-07-09): the impl is ONE uncondition...\n 2148|\n 2149|[[requirements]]\n 2150|id = \"REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE\"\n*2151|title = \"SEED (DEFERRED, doyle 2026-07-09 — post-counter-54 root-hardening for UPDATE-WEDGE; mint now, impl a FUTURE milestone): on a PLANNED brain-restart (`BRAIN_UPDATE_RESTART`, the seamless update-apply brain-cycle), the outgoing brain's LOCAL (by:None) controller conns are GRACEFULLY CLEAN-CLOSED as the brain is cycled, instead of hard-killed and left to black-hole. ROOT (field-pinned 2026-07-09, daemon.stderr.log L24277-24303): the update-restart path hard-kills the outgoing brain (`child.kill()`...\n 2152|required_stages = []  # SEED minted DEFERRED (doyle 2026-07-09); activate post-counter-54 at the milestone that delivers HITCH-FREE seamless updates. Supersedes the livehost-reattach 'Fix Y' framing (wrong site). The counter-54 fix (REQ-UPDATE-TRIAL-DRAIN-DRIVE reap-drive) makes updates SELF-HEAL in ~15s; this seed removes the residual hitch.\n 2153|\n 2154|[[requirements]]\n...\n 2160|id = \"REQ-BRAIN-RESUME-NO-CONTROL-STEAL\"\n*2161|title = \"UPDATE-WEDGE round 2 (v0.30.4, doyle-ruled 2026-07-09 — field incident on the counter-54 fetch--apply): a brain-respawn must NEVER steal, then stall-evict, the controller of a broker PTY session the daemon brain does not DRIVE. ROOT (field-pinned + SME, docs/UPDATE-WEDGE-2-RCA.md + docs/UPDATE-WEDGE-2-SME-todlando.md): `resume_sessions` (brain.rs:985) re-attaches EVERY session `KIND_SESSIONS` returns via `subscribe` = `subscribe_with(AttachIntent::Control, by:None)` (brain.rs:1450-1455). The d...\n 2162|required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED v0.30.4 UPDATE-WEDGE-2 fix (doyle ruled 2026-07-09; todlando builds). doc = brain.rs resume docstring corrected (kill the by:None-never-steals MYTH) + a CONTEXT/LIFECYCLE note that a brain-respawn re-attaches non-driven sessions as viewer. impl = resume path re-attaches AttachIntent::Viewer (Control only for genuinely-driven sessions, empty today). unit DROPPED if the impl is a branchless intent-flip (same FORK-2 reasoning as REQ-UPDATE-TRIAL-DRAIN-DR...\n 2163|\n 2164|[[requirements]]\n 2165|id = \"REQ-BRAIN-RESUME-NO-CONN-DEADLOCK\"\n*2166|title = \"UPDATE-WEDGE round 3 (v0.30.5, doyle-ruled Option A 2026-07-09 — the v0.30.4 field-verify re-wedge, root code-PROVEN + dead-peer-INDEPENDENT): the daemon brain must NOT subscribe broker PTY sessions onto its own request/reply IPC conn — it has no consumer for that output and the subscription DEADLOCKS the conn. ROOT (todlando code-read, docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md; the net-runtime AND the counter-54 reap-drive were both FALSIFIED first — docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md...\n 2167|required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED v0.30.5 UPDATE-WEDGE-3 fix (doyle ruled Option A 2026-07-09; todlando builds). doc = brain.rs resume_sessions docstring corrected (the brain no longer subscribes — D4-2/REQ-DAEMON-2 continuity intent) + a CONTEXT/LIFECYCLE note. impl = drop the subscribe_with call in resume_sessions (Option A). unit DROPPED (same FORK-2 reasoning as REQ-UPDATE-TRIAL-DRAIN-DRIVE: the impl is a deletion, no pure branch to unit-test; the int rig load-bearing-guards it ...\n 2168|\n 2169|[[requirements]]\n...\n 2299|id = \"REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK\"\n*2300|title = \"UPDATE-WEDGE round-4 (hertz RCA, root reproduced + source-pinned on Windows 2026-07-09): EVERY write on a physical broker connection rides ONE bounded + cancelable + poison-on-failure framed-write primitive — no writer may hold the connection's serialized send gate across an UNBOUNDED OS write. ROOT: controller_writer held the SharedSend = Arc<Mutex<SendHalf>> guard ACROSS a blocking write_frame; on Windows interprocess 2.4.2 routes the send to WriteFileEx + SleepEx(INFINITE, alertable) with N...\n 2301|required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED at mint, UPDATE-WEDGE round-4 (todlando 2026-07-09) per doyle GO. This ACTIVATES (and replaces, dedup at the v0.30.6 merge) the round-3 deferred SEED of the same id — r4 IS the Option-C class fix that SEED reserved; broker-side ⇒ lands via coordinated daemon restart, not a seamless brain-swap. doc = KNOWN-HAZARDS 7.38. impl = conn.rs BrokerConn (gate+watchdog+poison+abort) + full write-site routing sweep. int = brain_decouple.rs non_draining_contr...\n 2302|\n 2303|# ── MSG-IDENTITY milestone (operator GO 2026-07-10: \"two triages + prominent backlog seeds, especially messaging + endpoint identity\"; F-037 EXCLUDED by operator, covered later) ──\n...\n 2395|[[requirements]]\n*2396|id = \"REQ-DAEMON-REFRESH\"\n 2397|title = \"THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah d...\n 2398|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2399|\n...\n 2433|id = \"REQ-HAZARD-REDISPATCH-CONTROL-STEAL\"\n*2434|title = \"REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 — 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream — a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int — product...\n 2435|required_stages = [\"doc\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). doc = KNOWN-HAZARDS 7.41; int = redispatch.rs D1 (Served-retired historical attach) + D1b gate-round-1 (finished-UNRETIRED orderings: kill gen1 mid-serve THEN detach — pre-existing-backlog/brain-down-detach shape; gen2 skips + retires-on-sight, current controller unharmed across multi-second polls) — both production run_dispatch_loop via killable dispatch_fixture generations. HEAVY at birth, all three strings.\n 2436|\n 2437|[[requirements]]\n*2438|id = \"REQ-REDISPATCH-FINISHED-RETIRE\"\n*2439|title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bound...\n 2440|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16; int ADDED gate round 1; RULING gate round 2: retirement is ELIGIBILITY-VISIBILITY, never teardown — the \"lifecycle-exclude\" arm of the title, remove is REJECTED: the worker's reply send is fire-and-forget, so remove-on-retire races the still-queued reply inside the broker and resets the never-finished send half = requester torn-EOF/NoReply, proven 30/150 vs 0/150 A/B on kitsubito; physical teardown b...\n 2441|\n 2442|[[requirements]]\n*2443|id = \"REQ-STREAM-OPENER-DURABLE\"\n*2444|title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix B): stream classification identity is RESTART-DURABLE and independent of the evictable data ring — today reconstruction classifies via peek_first_line from ring seq 0, but StreamLog is a bounded 4096-transport-chunk ring (DEFAULT_STREAM_RING_CHUNKS, nethost.rs:111), so a high-traffic ACTIVE stream's opener/Request record evicts and replay classifies Unknown/Failed = active stream permanently abandoned. Pin an immutable bounded opener/classification fact...\n 2445|required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = StreamLog OpenerCapture (pinned outside the ring, bounded hunt) + net-stream-opener IPC + dispatcher opener-first classification (ring-peek fallback for the N-1 broker window) + serve_attach durable-Request seeding + floor_seq subscribe; unit = opener_pins_first_line_across_full_ring_roll + opener_capture_gives_up_bounded_on_a_newline_less_stream; int = redispatch.rs D2 (ring rolled via the SPT_STREAM...\n 2446|\n 2447|[[requirements]]\n*2448|id = \"REQ-DISPATCH-CLAIM-RETRY\"\n*2449|title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED — today claimed.insert(stream_id) happens PRE-spawn (dispatch.rs:206/214) and is never cleared or retried on Unknown/Failed, so one transient worker-setup failure permanently abandons the stream. Distinguish active/retryable vs served vs finished: a transient worker-setup failure releases/requeues the claim (bounded/backoff-shaped); terminal classification outcomes stay terminal and do NOT ...\n 2450|required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = ClaimState lifecycle (InFlight/Terminal/Retry, should_claim + outcome_transition pure seams, MAX 3 attempts, doubling backoff); unit = transient_failure_requeues_bounded_and_terminal_never_reenters (the no-hot-loop distinction in test); int = redispatch.rs D3 (SPT_DISPATCH_INJECT_TRANSIENT_ONCE valve — one injected failure, retry recovers, injection PROVEN fired in the generation log). HEAVY at birth.\n 2451|\n 2452|# ── MESH-RECOVERY (doyle triage 2026-07-16; ADR-0039; hertz RCA .claude/reports/2026-07-10-hertz-session/02-mesh-isolation-rca.md; KNOWN-HAZARDS 7.42). Stages flip at W1 build start (traceable-per-wave-activation). ──\n\n# docs/\n## KNOWN-HAZARDS.md\n 623|\n*624|### 7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection — a suspended brain conn must not wedge control  `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]`\n*625|<!-- [doc->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE] -->\n 626|- **Failure (paid-for, rig-CONFIRMED 2026-07-06/07 — `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When that brain is suspended (or black-holed) the write never returns. The output-driven eviction path (`append` → `mark_controller_gone`, bounded by 7.12's `CONTROLLER_WRITE_DEADLINE`) only fires on NEW output, and `reap_dead_controller` (7.29 companion) only caught a writer that had EXITED — so a contro...\n 627|- **Invariant:** no broker client ever observes a wedge because the BRAIN stalled. (1) The controller's writer publishes its IN-FLIGHT-write window (`write_blocked_since`: `Some(Instant)` set immediately before the blocking `write_frame`, `None` after — the mutex never held ACROSS the write); a reader sees a `Some` older than `BRAIN_WRITE_DEADLINE` (15 s = 3× the 7.12 controller bound, measured full-with-**zero writer progress**, so a slow-but-DRAINING brain never trips it) as a WEDGED writer and stal...\n 628|- **spt-core mapping:** `broker.rs` — `ControllerSink::write_blocked_since` + `controller_writer` (marks the in-flight window around both the initial-batch and live-loop writes) + `controller_write_stalled` (pure predicate over injected `now`/`deadline`) + `stall_evict_controller` (Inline stamp at `resolve_subscribe`, Deferred stamp at `reap_dead_controller` so the reap closure does no I/O under the shared sessions lock) + `record_stall_evict`/`stall_evict_stats` (the tally) + `dispatch_stall_evicts` (...\n## NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md\n 50|\n*51|**REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE** (doc, impl, unit, int)\n 52|Root, rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): PTY viewer fan-out\n 53|and control mutations depend synchronously on a live, DRAINING brain — brain-subscriber\n 54|session-output writes ride UNDER the per-session log lock (broker.rs:19-20). Failed writes are\n## REDISPATCH-TRUTH-TRIAGE.md\n 86|  7.41) + regression D1.\n*87|- **REQ-REDISPATCH-FINISHED-RETIRE** — mechanism A.\n*88|- **REQ-STREAM-OPENER-DURABLE** — mechanism B + regression D2.\n*89|- **REQ-DISPATCH-CLAIM-RETRY** — mechanism C + regression D3.\n 90|\n 91|Then: doyle gates → deployah releases → hertz field-verify on HFENDULEAM\n 92|(daemon refresh with active remote `spt rc` sessions: no frozen PTYs, no\n## RELEASE-RUNBOOK.md\n 52|     with no gap. Inserting the new heading with an editor whose anchor spans the\n*53|     previous heading can silently *consume* that heading (v0.34.0 did this to\n 54|     `## [0.33.0]`), folding the prior release's body into the new section — and\n 55|     since the body is published verbatim, the release notes then carry two\n 56|     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n## THE-FORKENING-TRIAGE.md\n 73|\n*74|## W4 — update UX (REQ-UPDATE-ADAPTERS-VERB, REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-RESTART-SAFE-SWAP, REQ-DAEMON-REFRESH) — queued\n 75|\n 76|Grounding fact (operator-corrected, code-confirmed): `update fetch --apply` /\n 77|`update apply` cycle the BRAIN only — the broker and every PTY survive\n## W2-BUILT-REPORT.md\n 2|\n*3|REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE, per ruling `docs/W2-DESIGN-RULING.md` @60ec0ed.\n 4|All four stages complete. Branch `lifecycle-truth` pushed, **wave head `fbf8ab7`**.\n 5|\n 6|## Commit ledger (this wave)\n...\n 69|  `broker` units 30/30 (incl the 4 W2 units), `driven_by_selfheal` + `control_stamp_lifetime` green.\n*70|- `traceable-reqs check` exit 0 — REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE `+doc +impl +unit +int`.\n 71|- per-wave activation (condition #4): REQ activated `[doc,impl,unit,int]` in the work-start\n 72|  range (mint carried the `[]` seed).\n 73|\n## W2-DESIGN-PROPOSAL.md\n 2|\n*3|Proposal for REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE (LIFECYCLE-TRUTH W2, triage\n 4|@907dd2d). doyle rules BEFORE impl; the REQ stays `required_stages = []` until then.\n 5|(Message parts of this proposal were eaten by the W5 delivery bug in transit —\n 6|this file is the authoritative copy; the numbered spt messages are superseded.)\n## W2-DESIGN-RULING.md\n 2|\n*3|Ruling on `docs/W2-DESIGN-PROPOSAL.md` @f3c6b4c (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE,\n 4|LIFECYCLE-TRUTH W2). Premises independently verified against the tree before ruling:\n 5|`ViewerSink`/`ControllerSink` + `CONTROLLER_WRITE_DEADLINE` (broker.rs:112/263/148),\n 6|blocking `send_frame` legs on the shared `SharedSend` across dispatch (broker.rs:2262–3415),\n...\n 71|   explicit eyes on `driven_by_selfheal` and every controller/viewer sink test.\n*72|4. **Per-wave activation** — activate REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE\n 73|   stages in the same commit that starts the work, not before, not the full\n 74|   end-state.\n 75|\n## W2-GATE-VERDICT.md\n 11|  5/5 green (flake ledger below); `traceable-reqs check` exit 0\n*12|  (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE `+doc +impl +unit +int`).\n 13|- Linux leg (kitsubito, /tmp/w2-gate @fbf8ab7): `brain_decouple` +\n 14|  `inject_control_wedge` + `driven_by_selfheal` 19/19; spt-daemon lib 451/451.\n 15|\n\n## adr/\n### 0038-broker-stream-lifecycle-truth.md\n 26|## Decision\n*27|<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\n 28|\n 29|Stream rows carry explicit **lifecycle truth**, and redispatch consumes it:\n 30|\n\n# crates/spt-daemon/\n## Cargo.toml\n 9|# `tests/redispatch.rs` (REQ-HAZARD-REDISPATCH-CONTROL-STEAL /\n*10|# REQ-STREAM-OPENER-DURABLE / REQ-DISPATCH-CLAIM-RETRY): the REAL\n 11|# `run_dispatch_loop` as a KILLABLE child process — a killed generation drops\n 12|# its workers' broker conns exactly like a production brain death. Same\n 13|# tests/-homed `[[bin]]` pattern as spt's fixtures (excluded from the release\n\n## src/\n### applyhost.rs\n 352|/// Cycle the brain of a RUNNING daemon with NO binary swap and NO staging\n*353|/// preconditions (REQ-DAEMON-REFRESH, `spt daemon refresh`): connect to the\n 354|/// broker and raise the same supervisor brain-restart signal the post-swap\n 355|/// apply rides ([`Brain::request_brain_restart`], ADR-0018 D3-3) — brain stop →\n 356|/// respawn → readiness trial → promote, broker + every hosted PTY untouched by\n...\n 362|/// (accepted, not acted on).\n*363|// [impl->REQ-DAEMON-REFRESH]\n 364|pub fn refresh_brain(broker_name: &str) -> Result<bool, String> {\n 365|    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\n 366|    brain\n...\n 822|\n*823|    // [unit->REQ-DAEMON-REFRESH] `refresh_brain` routes the SAME supervisor\n 824|    // brain-cycle signal as the post-swap apply — against a bare broker with NO\n 825|    // staged release, NO release cache, NO binary swap. The verb has no staging\n 826|    // preconditions: the signal raises and `honored` reports the supervisor\n### attach.rs\n 290|// [impl->REQ-RC-CROSS-NODE-ATTACH]\n*291|// [impl->REQ-STREAM-OPENER-DURABLE]\n 292|#[allow(clippy::too_many_arguments)]\n 293|pub fn serve_attach(\n 294|    brain: &mut Brain,\n### brain.rs\n 1155|    /// Query the broker's brain-subscriber stall-evict tally (LIFECYCLE-TRUTH W2\n*1156|    /// observability rider, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): `(count,\n 1157|    /// last_ms)` — how many controllers were stall-evicted for a suspended/black-holed\n 1158|    /// brain conn since boot, and when the most recent fired. `None` when the broker\n 1159|    /// is too old to answer (an older broker replies `unknown command kind`), read as\n...\n 1161|    /// [`broker_image_version`](Self::broker_image_version).\n*1162|    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1163|    pub fn stall_evicts(&mut self) -> io::Result<Option<(u64, u64)>> {\n 1164|        self.send(KIND_STALL_EVICTS, serde_json::Value::Null)?;\n 1165|        loop {\n...\n 1451|    /// broker to cycle onto the new binary).\n*1452|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1453|    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n 1454|        self.send(\n 1455|            KIND_NET_STREAM_RETIRE,\n...\n 1477|    /// the dispatcher falls back to the ring peek).\n*1478|    // [impl->REQ-STREAM-OPENER-DURABLE]\n 1479|    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n 1480|        self.send(\n 1481|            KIND_NET_STREAM_OPENER,\n### broker.rs\n 158|/// or the `KIND_SESSIONS` reap stall-evicts it (LIFECYCLE-TRUTH W2,\n*159|/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n 160|/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n 161|/// protects the fan-out against ONE per-client conn falling behind and is\n 162|/// output-driven (the drain observes a Full channel); THIS bound is measured\n...\n 170|/// is genuinely in flight past the deadline (see [`ControllerSink::write_blocked_since`]).\n*171|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 172|const BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n 173|\n 174|/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n...\n 179|/// ONLY — never relaxes an assertion.\n*180|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 181|fn brain_write_deadline() -> Duration {\n 182|    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n 183|        Ok(ms) => ms\n...\n 244|/// multi-session reap closure that forbids I/O under the shared sessions lock).\n*245|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 246|#[derive(Clone, Copy, PartialEq, Eq)]\n 247|enum StampMode {\n 248|    Inline,\n...\n 256|/// line, not a silent absence.\n*257|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 258|static STALL_EVICT_COUNT: AtomicU64 = AtomicU64::new(0);\n 259|static STALL_EVICT_LAST_MS: AtomicU64 = AtomicU64::new(0);\n 260|\n 261|/// Record one stall-evict in the broker-global tally (best-effort observability).\n*262|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 263|fn record_stall_evict() {\n 264|    STALL_EVICT_COUNT.fetch_add(1, Ordering::Relaxed);\n 265|    STALL_EVICT_LAST_MS.store(crate::brain::now_ms(), Ordering::Relaxed);\n...\n 270|/// surface (the observability rider).\n*271|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 272|pub(crate) fn stall_evict_stats() -> (u64, u64) {\n 273|    (\n 274|        STALL_EVICT_COUNT.load(Ordering::Relaxed),\n...\n 511|    /// The writer's IN-FLIGHT socket-write marker (LIFECYCLE-TRUTH W2,\n*512|    /// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): the `controller_writer` sets it to\n 513|    /// `Some(Instant::now())` immediately BEFORE it enters a blocking `write_frame`\n 514|    /// and back to `None` the instant the write returns. A reader\n 515|    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n...\n 1002|        }\n*1003|        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller whose\n 1004|        // writer is BLOCKED (not exited) on a suspended brain conn past\n 1005|        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n 1006|        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n...\n 1026|    /// Pure over the injected `now` + `deadline` so the unit tests it with no thread.\n*1027|    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1028|    fn controller_write_stalled(&self, now: Instant, deadline: Duration) -> bool {\n 1029|        self.controller.as_ref().is_some_and(|c| {\n 1030|            c.write_blocked_since\n...\n 1062|    /// `converge_perch_stamps` (the multi-session reap closure, KH 7.12 no-I/O rule).\n*1063|    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1064|    fn stall_evict_controller(&mut self, now: Instant, stamp: StampMode) -> bool {\n 1065|        let deadline = brain_write_deadline();\n 1066|        if !self.controller_write_stalled(now, deadline) {\n...\n 1074|             on a suspended/black-holed brain conn; detached, driven_by released \\\n*1075|             [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\",\n 1076|            self.session_id,\n 1077|            crate::conn::log_stamp(),\n 1078|            deadline.as_millis()\n...\n 1166|        // Control or Take.\n*1167|        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller WEDGED\n 1168|        // on a suspended/black-holed brain conn past BRAIN_WRITE_DEADLINE is\n 1169|        // stall-evicted HERE — the take/reattach self-heals against broker truth\n 1170|        // without waiting for new output (the output-driven mark_controller_gone\n...\n 1441|// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n*1442|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1443|fn note_controller_write_retired(sid: u64, conn: u64, e: &io::Error) {\n 1444|    if e.kind() != io::ErrorKind::TimedOut {\n 1445|        return;\n...\n 1450|         exceeded its bound; physical conn poisoned + retired (writer released, EOF \\\n*1451|         cleanup follows) [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\",\n 1452|        crate::conn::log_stamp()\n 1453|    );\n 1454|    record_stall_evict();\n...\n 1518|) {\n*1519|    // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): mark the exact window a\n 1520|    // BLOCKING socket write is in flight, so a reader can tell a WEDGED writer (brain\n 1521|    // conn suspended) from a merely idle one and stall-evict it. Set BEFORE the write,\n 1522|    // cleared AFTER — the mutex is never held across `write_frame` (that would just\n...\n 3875|    /// surface a brain that never returned as a diagnosable fact, not a silent absence.\n*3876|    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 3877|    fn dispatch_stall_evicts(&self, send: &SharedSend) {\n 3878|        let (count, last_ms) = stall_evict_stats();\n 3879|        let frame = Envelope::new(\n...\n 4318|    /// replay it (the KNOWN-HAZARDS 7.41 control-steal class).\n*4319|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 4320|    fn dispatch_net_stream_retire(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4321|        let req: NetStreamRetireReq = serde_json::from_value(env.payload)\n 4322|            .map_err(|e| format!(\"bad net-stream-retire payload: {e}\"))?;\n...\n 4338|    /// classification fact the dispatcher reads instead of peeking ring seq 0.\n*4339|    // [impl->REQ-STREAM-OPENER-DURABLE]\n 4340|    fn dispatch_net_stream_opener(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4341|        let req: NetStreamOpenerReq = serde_json::from_value(env.payload)\n 4342|            .map_err(|e| format!(\"bad net-stream-opener payload: {e}\"))?;\n...\n 5818|\n*5819|    // [unit->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE] the pure stall predicate: a\n 5820|    // writer BLOCKED in a socket write past BRAIN_WRITE_DEADLINE is stalled; an idle\n 5821|    // writer (write_blocked_since None) and a just-started write are NOT — so a\n 5822|    // slow-but-draining brain never evicts, only a genuinely wedged one.\n### dispatch.rs\n 169|/// A claimed stream's dispatch lifecycle (ADR-0038 C,\n*170|/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n 171|/// insert performed BEFORE worker spawn and never cleared — one transient\n 172|/// worker-setup failure permanently abandoned the stream. Claims now\n 173|/// distinguish in-flight vs terminal vs retryable, and a transient failure\n...\n 200|/// off). The unit-tested seam for the no-hot-loop ruling.\n*201|// [impl->REQ-DISPATCH-CLAIM-RETRY]\n 202|fn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n 203|    match state {\n 204|        None => Some(0),\n...\n 213|/// spent, then goes terminal.\n*214|// [impl->REQ-DISPATCH-CLAIM-RETRY]\n 215|fn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\n 216|    match outcome {\n 217|        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n...\n 250|/// else = a pending request — serve it.\n*251|// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 252|// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\n 253|fn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n 254|    finished && family == StreamFamily::Attach\n...\n 318|            // Terminal rows never re-enter; a released (Retry) claim re-enters\n*319|            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n 320|            if s.initiated_locally || s.next_seq == 0 {\n 321|                continue;\n 322|            }\n...\n 343|                if matches!(outcome, DispatchOutcome::Served(_)) {\n*344|                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n 345|                    // this stream's exchange is over — take its row out of\n 346|                    // every FUTURE dispatcher generation's enumeration (the\n 347|                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n...\n 388|/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\n*389|/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\n 390|/// it survives any ring roll — and fall back to the ring peek only when the\n 391|/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n 392|/// (terminal); `Err` = transient (the claim requeues bounded).\n...\n 576|                // gone, and a below-floor cursor trips the brain's gap check.\n*577|                // [impl->REQ-STREAM-OPENER-DURABLE]\n 578|                Some(endpoint) => match serve_attach(\n 579|                    &mut brain,\n 580|                    broker_name,\n...\n 959|\n*960|    // [unit->REQ-DISPATCH-CLAIM-RETRY] the claim lifecycle, at its pure seam:\n 961|    // a TRANSIENT failure releases the claim and requeues it after a backoff;\n 962|    // TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter\n 963|    // — a persistently-failing stream retries exactly its bounded budget and\n...\n 1002|\n*1003|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] the lifecycle+family\n 1004|    // discriminator (gate round 1): a finished ATTACH is terminal for\n 1005|    // redispatch (the operator detached — replay = the 7.41 controller\n 1006|    // steal), while a finished REQUEST/REPLY stream is NOT — sync and update\n### msg.rs\n 102|/// Brain→broker query: the brain-subscriber stall-evict tally (LIFECYCLE-TRUTH W2\n*103|/// observability rider, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) — how many\n 104|/// controllers were stall-evicted for a suspended/black-holed brain conn since\n 105|/// boot, and when the most recent fired. Surfaced on `daemon status` so a brain\n 106|/// that never returns is a diagnosable fact. An older broker predating this kind\n...\n 152|/// the caller treats retire as best-effort).\n*153|// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 154|pub const KIND_NET_STREAM_RETIRE: &str = \"net-stream-retire\";\n 155|/// Broker→brain: the retire ack (`removed` = a row actually left the table).\n 156|pub const KIND_NET_STREAM_RETIRED: &str = \"net-stream-retired\";\n...\n 160|/// answers `error` (KH-2.3: the dispatcher falls back to the ring peek).\n*161|// [impl->REQ-STREAM-OPENER-DURABLE]\n 162|pub const KIND_NET_STREAM_OPENER: &str = \"net-stream-opener\";\n 163|/// Broker→brain: the pinned opener reply (`opener_b64 = None` ⇒ not pinned:\n 164|/// first newline not yet arrived, or capture gave up bounded).\n...\n 570|\n*571|/// `stall-evicts-reply` payload (LIFECYCLE-TRUTH W2, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE):\n 572|/// the broker-global brain-subscriber stall-evict tally. Both fields defaulted so a\n 573|/// truncated/older reply still decodes (2.3 forward-compat).\n 574|#[derive(Debug, Clone, Serialize, Deserialize)]\n...\n 792|    /// broker omits it → `0`, today's subscribe-from-0 behavior.\n*793|    // [impl->REQ-STREAM-OPENER-DURABLE]\n 794|    #[serde(default)]\n 795|    pub floor_seq: u64,\n 796|}\n### nethost.rs\n 121|\n*122|/// Restart-durable opener capture (ADR-0038 B, REQ-STREAM-OPENER-DURABLE):\n 123|/// the stream's complete first NDJSON line, pinned OUTSIDE the evictable data\n 124|/// ring and held until the stream row goes away. Recovery classifies from\n 125|/// this fact, never from ring seq 0 (which a bounded ring evicts under\n...\n 254|        // `Oversize` are terminal states.\n*255|        // [impl->REQ-STREAM-OPENER-DURABLE]\n 256|        if let OpenerCapture::Buffering(buf) = &mut self.opener {\n 257|            match bytes.iter().position(|&b| b == b'\\n') {\n 258|                Some(nl) => {\n...\n 544|    /// is load-bearing; physical teardown belongs to the conn-close sweep only.\n*545|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 546|    retired: std::sync::atomic::AtomicBool,\n 547|}\n 548|\n...\n 707|        // else reads a dead conn's stream ring.\n*708|        // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 709|        sh_close\n 710|            .streams\n 711|            .lock()\n...\n 1560|    /// call newly retired the row (idempotent; absent row = false).\n*1561|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1562|    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1563|        let Some(entry) = self\n 1564|            .shared\n...\n 1587|    /// not yet arrived, or capture gave up bounded).\n*1588|    // [impl->REQ-STREAM-OPENER-DURABLE]\n 1589|    pub fn stream_opener(&self, stream_id: u64) -> Option<Option<Vec<u8>>> {\n 1590|        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\n 1591|        // A retired row answers no-such-stream: classification exists only to\n...\n 2053|\n*2054|    // [unit->REQ-STREAM-OPENER-DURABLE] the opener is pinned OUTSIDE the\n 2055|    // evictable ring: a small-cap log rolls its ring completely (floor\n 2056|    // advances past 0), yet the pinned first line — including one split\n 2057|    // across appends — survives verbatim. This is the fact recovery\n...\n 2078|\n*2079|    // [unit->REQ-STREAM-OPENER-DURABLE] capture is BOUNDED: a stream whose\n 2080|    // first bytes never carry a newline gives up at the cap (Oversize is\n 2081|    // terminal — a late newline cannot resurrect it), so a non-NDJSON stream\n 2082|    // costs one bounded buffer, never unbounded memory.\n...\n 2091|\n*2092|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the\n 2093|    // named row from dispatch eligibility: the retired stream leaves the\n 2094|    // dispatch enumeration (stream_infos) while the active sibling stays\n 2095|    // claimable, a second retire is a no-op, and the pinned-opener query\n...\n 2130|\n*2131|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement never severs the\n 2132|    // transport: a reply sent AFTER the row was retired still flushes to the\n 2133|    // peer. This is the field race's lost ordering made deterministic — the\n 2134|    // worker's reply send is fire-and-forget (brain returns before the broker\n...\n 2176|\n*2177|    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] a dead CONNECTION retires its\n 2178|    // stream rows with it: the closed-watcher sweep leaves nothing for a\n 2179|    // future dispatcher generation to replay on a transport that can never\n 2180|    // serve again (the field bug's historical-rows-forever half).\n\n## tests/\n### brain_decouple.rs\n*1|//! LIFECYCLE-TRUTH W2 (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) — the suspend-brain\n 2|//! int rig, on a REAL broker hosting a REAL PTY child over the real local IPC.\n 3|//!\n 4|//! ## The bug this gate locks down (the update-wedge root)\n...\n 219|\n*220|// [int->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 221|#[test]\n 222|fn suspended_brain_controller_is_stall_evicted_take_completes_viewer_ticks() {\n 223|    init_home();\n\nShowing files 1-20 of 25+. Use skip=20 for the next page, or narrow paths/pattern.\n\nSkipped missing paths: requirements\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"traceable-reqs.toml, docs, crates, CHANGELOG.md, Cargo.toml","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":87,"fileCount":20,"files":["traceable-reqs.toml","docs/KNOWN-HAZARDS.md","docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md","docs/REDISPATCH-TRUTH-TRIAGE.md","docs/RELEASE-RUNBOOK.md","docs/THE-FORKENING-TRIAGE.md","docs/W2-BUILT-REPORT.md","docs/W2-DESIGN-PROPOSAL.md","docs/W2-DESIGN-RULING.md","docs/W2-GATE-VERDICT.md","docs/adr/0038-broker-stream-lifecycle-truth.md","crates/spt-daemon/Cargo.toml","crates/spt-daemon/src/applyhost.rs","crates/spt-daemon/src/attach.rs","crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/msg.rs","crates/spt-daemon/src/nethost.rs","crates/spt-daemon/tests/brain_decouple.rs"],"fileMatches":[{"path":"traceable-reqs.toml","count":15},{"path":"docs/KNOWN-HAZARDS.md","count":2},{"path":"docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md","count":1},{"path":"docs/REDISPATCH-TRUTH-TRIAGE.md","count":3},{"path":"docs/RELEASE-RUNBOOK.md","count":1},{"path":"docs/THE-FORKENING-TRIAGE.md","count":1},{"path":"docs/W2-BUILT-REPORT.md","count":2},{"path":"docs/W2-DESIGN-PROPOSAL.md","count":1},{"path":"docs/W2-DESIGN-RULING.md","count":2},{"path":"docs/W2-GATE-VERDICT.md","count":1},{"path":"docs/adr/0038-broker-stream-lifecycle-truth.md","count":1},{"path":"crates/spt-daemon/Cargo.toml","count":1},{"path":"crates/spt-daemon/src/applyhost.rs","count":3},{"path":"crates/spt-daemon/src/attach.rs","count":1},{"path":"crates/spt-daemon/src/brain.rs","count":4},{"path":"crates/spt-daemon/src/broker.rs","count":20},{"path":"crates/spt-daemon/src/dispatch.rs","count":10},{"path":"crates/spt-daemon/src/msg.rs","count":5},{"path":"crates/spt-daemon/src/nethost.rs","count":11},{"path":"crates/spt-daemon/tests/brain_decouple.rs","count":2}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# traceable-reqs.toml\n 2109│[[requirements]]\n*2110│id = \"REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE\"\n 2111│title = \"W2 (LIFECYCLE-TRUTH, KNOWN-HAZARDS, flagship — the update wedge): PTY viewer fan-out and control mutations must not depend synchronously on a live draining brain. ROOT rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): brain-subscriber session-output writes ride UNDER the per-session log lock (broker.rs:19-20); failed writes are handled (cursor freeze + detach :3486) but BLOCKED writes are not. Suspended brain => within seconds attached rc output freezes; detach does NOT releas...\n 2112│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED LIFECYCLE-TRUTH W2 (todlando 2026-07-07) per doyle ruling docs/W2-DESIGN-RULING.md @60ec0ed (minimal-plus scope). doc = KNOWN-HAZARDS entry (\"PTY fan-out + broker control plane never block on any single subscriber conn — bounded sinks everywhere incl. the brain conn; brain.ready != subscribers drained\"). impl = (a) bounded brain-conn sink (writer thread + bounded queue + BRAIN_WRITE_DEADLINE=15s deadline-evict, full-with-zero-progress last_o...\n 2113│\n     │...\n 2140│id = \"REQ-UPDATE-PROMOTE-DRAINED\"\n*2141│title = \"W3 (LIFECYCLE-TRUTH, mechanic-d MOVED FROM W2 per doyle gate verdict @e5ae7a9 — binding): the update-apply brain-generation promotion completes only when the OLD generation's broker subscriber connection is CLOSED or stall-EVICTED — never while blocked writes still pend on it. ROOT: `brain.ready` != subscribers drained; W2's stall-evict (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) only BOUNDS the false-promote window to BRAIN_WRITE_DEADLINE (15s), it does NOT close it — a new brain can signal...\n 2142│required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED LIFECYCLE-TRUTH W3 drained-condition slice (todlando 2026-07-07, work-start); brainproc.rs run_trial DRAINED gate + broker any_local_controller_wedged truth + false-promote int rig — closes the W2 15s residual (KH 7.36)\n 2143│\n 2144│[[requirements]]\n 2145│id = \"REQ-UPDATE-TRIAL-DRAIN-DRIVE\"\n*2146│title = \"UPDATE-WEDGE (counter-54, doyle-ruled 2026-07-09 — regression of the v0.29.0 seamless brain-swap): a brain generation DRIVES the broker's controller-liveness reap (a KIND_SESSIONS poll) each heartbeat throughout its boot/trial loop, so a hard-KILLED prior generation's black-holed LOCAL controller conn (by:None) is stall-evicted within the trial window and can never permanently strand the promotion DRAINED gate. ROOT (2026-07-09 field freeze, `spt update fetch --apply` v0.30.0->v0.30.2 froze al...\n 2147│required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED counter-54 UPDATE-WEDGE fix (doyle ruled brain-side 2026-07-09; todlando builds). doc = a CONTEXT.md/LIFECYCLE note that the trial/boot brain drives controller-reap so a killed prior generation cannot strand old_gen_drained. impl = the brain.sessions() KIND_SESSIONS drive in run_brain's heartbeat loop (brainproc.rs, first-tick + every tick, trial + recovery brain). UNIT DELIBERATELY DROPPED (doyle FORK-2 ruling 2026-07-09): the impl is ONE uncondition...\n 2148│\n 2149│[[requirements]]\n 2150│id = \"REQ-BRAIN-UPDATE-RESTART-CLEAN-CLOSE\"\n*2151│title = \"SEED (DEFERRED, doyle 2026-07-09 — post-counter-54 root-hardening for UPDATE-WEDGE; mint now, impl a FUTURE milestone): on a PLANNED brain-restart (`BRAIN_UPDATE_RESTART`, the seamless update-apply brain-cycle), the outgoing brain's LOCAL (by:None) controller conns are GRACEFULLY CLEAN-CLOSED as the brain is cycled, instead of hard-killed and left to black-hole. ROOT (field-pinned 2026-07-09, daemon.stderr.log L24277-24303): the update-restart path hard-kills the outgoing brain (`child.kill()`...\n 2152│required_stages = []  # SEED minted DEFERRED (doyle 2026-07-09); activate post-counter-54 at the milestone that delivers HITCH-FREE seamless updates. Supersedes the livehost-reattach 'Fix Y' framing (wrong site). The counter-54 fix (REQ-UPDATE-TRIAL-DRAIN-DRIVE reap-drive) makes updates SELF-HEAL in ~15s; this seed removes the residual hitch.\n 2153│\n 2154│[[requirements]]\n     │...\n 2160│id = \"REQ-BRAIN-RESUME-NO-CONTROL-STEAL\"\n*2161│title = \"UPDATE-WEDGE round 2 (v0.30.4, doyle-ruled 2026-07-09 — field incident on the counter-54 fetch--apply): a brain-respawn must NEVER steal, then stall-evict, the controller of a broker PTY session the daemon brain does not DRIVE. ROOT (field-pinned + SME, docs/UPDATE-WEDGE-2-RCA.md + docs/UPDATE-WEDGE-2-SME-todlando.md): `resume_sessions` (brain.rs:985) re-attaches EVERY session `KIND_SESSIONS` returns via `subscribe` = `subscribe_with(AttachIntent::Control, by:None)` (brain.rs:1450-1455). The d...\n 2162│required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED v0.30.4 UPDATE-WEDGE-2 fix (doyle ruled 2026-07-09; todlando builds). doc = brain.rs resume docstring corrected (kill the by:None-never-steals MYTH) + a CONTEXT/LIFECYCLE note that a brain-respawn re-attaches non-driven sessions as viewer. impl = resume path re-attaches AttachIntent::Viewer (Control only for genuinely-driven sessions, empty today). unit DROPPED if the impl is a branchless intent-flip (same FORK-2 reasoning as REQ-UPDATE-TRIAL-DRAIN-DR...\n 2163│\n 2164│[[requirements]]\n 2165│id = \"REQ-BRAIN-RESUME-NO-CONN-DEADLOCK\"\n*2166│title = \"UPDATE-WEDGE round 3 (v0.30.5, doyle-ruled Option A 2026-07-09 — the v0.30.4 field-verify re-wedge, root code-PROVEN + dead-peer-INDEPENDENT): the daemon brain must NOT subscribe broker PTY sessions onto its own request/reply IPC conn — it has no consumer for that output and the subscription DEADLOCKS the conn. ROOT (todlando code-read, docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md; the net-runtime AND the counter-54 reap-drive were both FALSIFIED first — docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md...\n 2167│required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED v0.30.5 UPDATE-WEDGE-3 fix (doyle ruled Option A 2026-07-09; todlando builds). doc = brain.rs resume_sessions docstring corrected (the brain no longer subscribes — D4-2/REQ-DAEMON-2 continuity intent) + a CONTEXT/LIFECYCLE note. impl = drop the subscribe_with call in resume_sessions (Option A). unit DROPPED (same FORK-2 reasoning as REQ-UPDATE-TRIAL-DRAIN-DRIVE: the impl is a deletion, no pure branch to unit-test; the int rig load-bearing-guards it ...\n 2168│\n 2169│[[requirements]]\n     │...\n 2299│id = \"REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK\"\n*2300│title = \"UPDATE-WEDGE round-4 (hertz RCA, root reproduced + source-pinned on Windows 2026-07-09): EVERY write on a physical broker connection rides ONE bounded + cancelable + poison-on-failure framed-write primitive — no writer may hold the connection's serialized send gate across an UNBOUNDED OS write. ROOT: controller_writer held the SharedSend = Arc<Mutex<SendHalf>> guard ACROSS a blocking write_frame; on Windows interprocess 2.4.2 routes the send to WriteFileEx + SleepEx(INFINITE, alertable) with N...\n 2301│required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED at mint, UPDATE-WEDGE round-4 (todlando 2026-07-09) per doyle GO. This ACTIVATES (and replaces, dedup at the v0.30.6 merge) the round-3 deferred SEED of the same id — r4 IS the Option-C class fix that SEED reserved; broker-side ⇒ lands via coordinated daemon restart, not a seamless brain-swap. doc = KNOWN-HAZARDS 7.38. impl = conn.rs BrokerConn (gate+watchdog+poison+abort) + full write-site routing sweep. int = brain_decouple.rs non_draining_contr...\n 2302│\n 2303│# ── MSG-IDENTITY milestone (operator GO 2026-07-10: \"two triages + prominent backlog seeds, especially messaging + endpoint identity\"; F-037 EXCLUDED by operator, covered later) ──\n     │...\n 2395│[[requirements]]\n*2396│id = \"REQ-DAEMON-REFRESH\"\n 2397│title = \"THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah d...\n 2398│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2399│\n     │...\n 2433│id = \"REQ-HAZARD-REDISPATCH-CONTROL-STEAL\"\n*2434│title = \"REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 — 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream — a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int — product...\n 2435│required_stages = [\"doc\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). doc = KNOWN-HAZARDS 7.41; int = redispatch.rs D1 (Served-retired historical attach) + D1b gate-round-1 (finished-UNRETIRED orderings: kill gen1 mid-serve THEN detach — pre-existing-backlog/brain-down-detach shape; gen2 skips + retires-on-sight, current controller unharmed across multi-second polls) — both production run_dispatch_loop via killable dispatch_fixture generations. HEAVY at birth, all three strings.\n 2436│\n 2437│[[requirements]]\n*2438│id = \"REQ-REDISPATCH-FINISHED-RETIRE\"\n*2439│title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bound...\n 2440│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16; int ADDED gate round 1; RULING gate round 2: retirement is ELIGIBILITY-VISIBILITY, never teardown — the \"lifecycle-exclude\" arm of the title, remove is REJECTED: the worker's reply send is fire-and-forget, so remove-on-retire races the still-queued reply inside the broker and resets the never-finished send half = requester torn-EOF/NoReply, proven 30/150 vs 0/150 A/B on kitsubito; physical teardown b...\n 2441│\n 2442│[[requirements]]\n*2443│id = \"REQ-STREAM-OPENER-DURABLE\"\n*2444│title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix B): stream classification identity is RESTART-DURABLE and independent of the evictable data ring — today reconstruction classifies via peek_first_line from ring seq 0, but StreamLog is a bounded 4096-transport-chunk ring (DEFAULT_STREAM_RING_CHUNKS, nethost.rs:111), so a high-traffic ACTIVE stream's opener/Request record evicts and replay classifies Unknown/Failed = active stream permanently abandoned. Pin an immutable bounded opener/classification fact...\n 2445│required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = StreamLog OpenerCapture (pinned outside the ring, bounded hunt) + net-stream-opener IPC + dispatcher opener-first classification (ring-peek fallback for the N-1 broker window) + serve_attach durable-Request seeding + floor_seq subscribe; unit = opener_pins_first_line_across_full_ring_roll + opener_capture_gives_up_bounded_on_a_newline_less_stream; int = redispatch.rs D2 (ring rolled via the SPT_STREAM...\n 2446│\n 2447│[[requirements]]\n*2448│id = \"REQ-DISPATCH-CLAIM-RETRY\"\n*2449│title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED — today claimed.insert(stream_id) happens PRE-spawn (dispatch.rs:206/214) and is never cleared or retried on Unknown/Failed, so one transient worker-setup failure permanently abandons the stream. Distinguish active/retryable vs served vs finished: a transient worker-setup failure releases/requeues the claim (bounded/backoff-shaped); terminal classification outcomes stay terminal and do NOT ...\n 2450│required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = ClaimState lifecycle (InFlight/Terminal/Retry, should_claim + outcome_transition pure seams, MAX 3 attempts, doubling backoff); unit = transient_failure_requeues_bounded_and_terminal_never_reenters (the no-hot-loop distinction in test); int = redispatch.rs D3 (SPT_DISPATCH_INJECT_TRANSIENT_ONCE valve — one injected failure, retry recovers, injection PROVEN fired in the generation log). HEAVY at birth.\n 2451│\n 2452│# ── MESH-RECOVERY (doyle triage 2026-07-16; ADR-0039; hertz RCA .claude/reports/2026-07-10-hertz-session/02-mesh-isolation-rca.md; KNOWN-HAZARDS 7.42). Stages flip at W1 build start (traceable-per-wave-activation). ──\n\n# docs/\n## KNOWN-HAZARDS.md\n 623│\n*624│### 7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection — a suspended brain conn must not wedge control  `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]`\n*625│<!-- [doc->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE] -->\n 626│- **Failure (paid-for, rig-CONFIRMED 2026-07-06/07 — `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When that brain is suspended (or black-holed) the write never returns. The output-driven eviction path (`append` → `mark_controller_gone`, bounded by 7.12's `CONTROLLER_WRITE_DEADLINE`) only fires on NEW output, and `reap_dead_controller` (7.29 companion) only caught a writer that had EXITED — so a contro...\n 627│- **Invariant:** no broker client ever observes a wedge because the BRAIN stalled. (1) The controller's writer publishes its IN-FLIGHT-write window (`write_blocked_since`: `Some(Instant)` set immediately before the blocking `write_frame`, `None` after — the mutex never held ACROSS the write); a reader sees a `Some` older than `BRAIN_WRITE_DEADLINE` (15 s = 3× the 7.12 controller bound, measured full-with-**zero writer progress**, so a slow-but-DRAINING brain never trips it) as a WEDGED writer and stal...\n 628│- **spt-core mapping:** `broker.rs` — `ControllerSink::write_blocked_since` + `controller_writer` (marks the in-flight window around both the initial-batch and live-loop writes) + `controller_write_stalled` (pure predicate over injected `now`/`deadline`) + `stall_evict_controller` (Inline stamp at `resolve_subscribe`, Deferred stamp at `reap_dead_controller` so the reap closure does no I/O under the shared sessions lock) + `record_stall_evict`/`stall_evict_stats` (the tally) + `dispatch_stall_evicts` (...\n## NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md\n 50│\n*51│**REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE** (doc, impl, unit, int)\n 52│Root, rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): PTY viewer fan-out\n 53│and control mutations depend synchronously on a live, DRAINING brain — brain-subscriber\n 54│session-output writes ride UNDER the per-session log lock (broker.rs:19-20). Failed writes are\n## REDISPATCH-TRUTH-TRIAGE.md\n 86│  7.41) + regression D1.\n*87│- **REQ-REDISPATCH-FINISHED-RETIRE** — mechanism A.\n*88│- **REQ-STREAM-OPENER-DURABLE** — mechanism B + regression D2.\n*89│- **REQ-DISPATCH-CLAIM-RETRY** — mechanism C + regression D3.\n 90│\n 91│Then: doyle gates → deployah releases → hertz field-verify on HFENDULEAM\n 92│(daemon refresh with active remote `spt rc` sessions: no frozen PTYs, no\n## RELEASE-RUNBOOK.md\n 52│     with no gap. Inserting the new heading with an editor whose anchor spans the\n*53│     previous heading can silently *consume* that heading (v0.34.0 did this to\n 54│     `## [0.33.0]`), folding the prior release's body into the new section — and\n 55│     since the body is published verbatim, the release notes then carry two\n 56│     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n## THE-FORKENING-TRIAGE.md\n 73│\n*74│## W4 — update UX (REQ-UPDATE-ADAPTERS-VERB, REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-RESTART-SAFE-SWAP, REQ-DAEMON-REFRESH) — queued\n 75│\n 76│Grounding fact (operator-corrected, code-confirmed): `update fetch --apply` /\n 77│`update apply` cycle the BRAIN only — the broker and every PTY survive\n## W2-BUILT-REPORT.md\n  2│\n *3│REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE, per ruling `docs/W2-DESIGN-RULING.md` @60ec0ed.\n  4│All four stages complete. Branch `lifecycle-truth` pushed, **wave head `fbf8ab7`**.\n  5│\n  6│## Commit ledger (this wave)\n   │...\n 69│  `broker` units 30/30 (incl the 4 W2 units), `driven_by_selfheal` + `control_stamp_lifetime` green.\n*70│- `traceable-reqs check` exit 0 — REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE `+doc +impl +unit +int`.\n 71│- per-wave activation (condition #4): REQ activated `[doc,impl,unit,int]` in the work-start\n 72│  range (mint carried the `[]` seed).\n 73│\n## W2-DESIGN-PROPOSAL.md\n 2│\n*3│Proposal for REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE (LIFECYCLE-TRUTH W2, triage\n 4│@907dd2d). doyle rules BEFORE impl; the REQ stays `required_stages = []` until then.\n 5│(Message parts of this proposal were eaten by the W5 delivery bug in transit —\n 6│this file is the authoritative copy; the numbered spt messages are superseded.)\n## W2-DESIGN-RULING.md\n  2│\n *3│Ruling on `docs/W2-DESIGN-PROPOSAL.md` @f3c6b4c (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE,\n  4│LIFECYCLE-TRUTH W2). Premises independently verified against the tree before ruling:\n  5│`ViewerSink`/`ControllerSink` + `CONTROLLER_WRITE_DEADLINE` (broker.rs:112/263/148),\n  6│blocking `send_frame` legs on the shared `SharedSend` across dispatch (broker.rs:2262–3415),\n   │...\n 71│   explicit eyes on `driven_by_selfheal` and every controller/viewer sink test.\n*72│4. **Per-wave activation** — activate REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE\n 73│   stages in the same commit that starts the work, not before, not the full\n 74│   end-state.\n 75│\n## W2-GATE-VERDICT.md\n 11│  5/5 green (flake ledger below); `traceable-reqs check` exit 0\n*12│  (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE `+doc +impl +unit +int`).\n 13│- Linux leg (kitsubito, /tmp/w2-gate @fbf8ab7): `brain_decouple` +\n 14│  `inject_control_wedge` + `driven_by_selfheal` 19/19; spt-daemon lib 451/451.\n 15│\n\n## adr/\n### 0038-broker-stream-lifecycle-truth.md\n 26│## Decision\n*27│<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\n 28│\n 29│Stream rows carry explicit **lifecycle truth**, and redispatch consumes it:\n 30│\n\n# crates/spt-daemon/\n## Cargo.toml\n  9│# `tests/redispatch.rs` (REQ-HAZARD-REDISPATCH-CONTROL-STEAL /\n*10│# REQ-STREAM-OPENER-DURABLE / REQ-DISPATCH-CLAIM-RETRY): the REAL\n 11│# `run_dispatch_loop` as a KILLABLE child process — a killed generation drops\n 12│# its workers' broker conns exactly like a production brain death. Same\n 13│# tests/-homed `[[bin]]` pattern as spt's fixtures (excluded from the release\n\n## src/\n### applyhost.rs\n 352│/// Cycle the brain of a RUNNING daemon with NO binary swap and NO staging\n*353│/// preconditions (REQ-DAEMON-REFRESH, `spt daemon refresh`): connect to the\n 354│/// broker and raise the same supervisor brain-restart signal the post-swap\n 355│/// apply rides ([`Brain::request_brain_restart`], ADR-0018 D3-3) — brain stop →\n 356│/// respawn → readiness trial → promote, broker + every hosted PTY untouched by\n    │...\n 362│/// (accepted, not acted on).\n*363│// [impl->REQ-DAEMON-REFRESH]\n 364│pub fn refresh_brain(broker_name: &str) -> Result<bool, String> {\n 365│    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\n 366│    brain\n    │...\n 822│\n*823│    // [unit->REQ-DAEMON-REFRESH] `refresh_brain` routes the SAME supervisor\n 824│    // brain-cycle signal as the post-swap apply — against a bare broker with NO\n 825│    // staged release, NO release cache, NO binary swap. The verb has no staging\n 826│    // preconditions: the signal raises and `honored` reports the supervisor\n### attach.rs\n 290│// [impl->REQ-RC-CROSS-NODE-ATTACH]\n*291│// [impl->REQ-STREAM-OPENER-DURABLE]\n 292│#[allow(clippy::too_many_arguments)]\n 293│pub fn serve_attach(\n 294│    brain: &mut Brain,\n### brain.rs\n 1155│    /// Query the broker's brain-subscriber stall-evict tally (LIFECYCLE-TRUTH W2\n*1156│    /// observability rider, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): `(count,\n 1157│    /// last_ms)` — how many controllers were stall-evicted for a suspended/black-holed\n 1158│    /// brain conn since boot, and when the most recent fired. `None` when the broker\n 1159│    /// is too old to answer (an older broker replies `unknown command kind`), read as\n     │...\n 1161│    /// [`broker_image_version`](Self::broker_image_version).\n*1162│    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1163│    pub fn stall_evicts(&mut self) -> io::Result<Option<(u64, u64)>> {\n 1164│        self.send(KIND_STALL_EVICTS, serde_json::Value::Null)?;\n 1165│        loop {\n     │...\n 1451│    /// broker to cycle onto the new binary).\n*1452│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1453│    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n 1454│        self.send(\n 1455│            KIND_NET_STREAM_RETIRE,\n     │...\n 1477│    /// the dispatcher falls back to the ring peek).\n*1478│    // [impl->REQ-STREAM-OPENER-DURABLE]\n 1479│    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n 1480│        self.send(\n 1481│            KIND_NET_STREAM_OPENER,\n### broker.rs\n  158│/// or the `KIND_SESSIONS` reap stall-evicts it (LIFECYCLE-TRUTH W2,\n *159│/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n  160│/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n  161│/// protects the fan-out against ONE per-client conn falling behind and is\n  162│/// output-driven (the drain observes a Full channel); THIS bound is measured\n     │...\n  170│/// is genuinely in flight past the deadline (see [`ControllerSink::write_blocked_since`]).\n *171│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n  172│const BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n  173│\n  174│/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n     │...\n  179│/// ONLY — never relaxes an assertion.\n *180│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n  181│fn brain_write_deadline() -> Duration {\n  182│    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n  183│        Ok(ms) => ms\n     │...\n  244│/// multi-session reap closure that forbids I/O under the shared sessions lock).\n *245│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n  246│#[derive(Clone, Copy, PartialEq, Eq)]\n  247│enum StampMode {\n  248│    Inline,\n     │...\n  256│/// line, not a silent absence.\n *257│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n  258│static STALL_EVICT_COUNT: AtomicU64 = AtomicU64::new(0);\n  259│static STALL_EVICT_LAST_MS: AtomicU64 = AtomicU64::new(0);\n  260│\n  261│/// Record one stall-evict in the broker-global tally (best-effort observability).\n *262│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n  263│fn record_stall_evict() {\n  264│    STALL_EVICT_COUNT.fetch_add(1, Ordering::Relaxed);\n  265│    STALL_EVICT_LAST_MS.store(crate::brain::now_ms(), Ordering::Relaxed);\n     │...\n  270│/// surface (the observability rider).\n *271│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n  272│pub(crate) fn stall_evict_stats() -> (u64, u64) {\n  273│    (\n  274│        STALL_EVICT_COUNT.load(Ordering::Relaxed),\n     │...\n  511│    /// The writer's IN-FLIGHT socket-write marker (LIFECYCLE-TRUTH W2,\n *512│    /// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): the `controller_writer` sets it to\n  513│    /// `Some(Instant::now())` immediately BEFORE it enters a blocking `write_frame`\n  514│    /// and back to `None` the instant the write returns. A reader\n  515│    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n     │...\n 1002│        }\n*1003│        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller whose\n 1004│        // writer is BLOCKED (not exited) on a suspended brain conn past\n 1005│        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n 1006│        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n     │...\n 1026│    /// Pure over the injected `now` + `deadline` so the unit tests it with no thread.\n*1027│    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1028│    fn controller_write_stalled(&self, now: Instant, deadline: Duration) -> bool {\n 1029│        self.controller.as_ref().is_some_and(|c| {\n 1030│            c.write_blocked_since\n     │...\n 1062│    /// `converge_perch_stamps` (the multi-session reap closure, KH 7.12 no-I/O rule).\n*1063│    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1064│    fn stall_evict_controller(&mut self, now: Instant, stamp: StampMode) -> bool {\n 1065│        let deadline = brain_write_deadline();\n 1066│        if !self.controller_write_stalled(now, deadline) {\n     │...\n 1074│             on a suspended/black-holed brain conn; detached, driven_by released \\\n*1075│             [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\",\n 1076│            self.session_id,\n 1077│            crate::conn::log_stamp(),\n 1078│            deadline.as_millis()\n     │...\n 1166│        // Control or Take.\n*1167│        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller WEDGED\n 1168│        // on a suspended/black-holed brain conn past BRAIN_WRITE_DEADLINE is\n 1169│        // stall-evicted HERE — the take/reattach self-heals against broker truth\n 1170│        // without waiting for new output (the output-driven mark_controller_gone\n     │...\n 1441│// [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n*1442│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 1443│fn note_controller_write_retired(sid: u64, conn: u64, e: &io::Error) {\n 1444│    if e.kind() != io::ErrorKind::TimedOut {\n 1445│        return;\n     │...\n 1450│         exceeded its bound; physical conn poisoned + retired (writer released, EOF \\\n*1451│         cleanup follows) [REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\",\n 1452│        crate::conn::log_stamp()\n 1453│    );\n 1454│    record_stall_evict();\n     │...\n 1518│) {\n*1519│    // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): mark the exact window a\n 1520│    // BLOCKING socket write is in flight, so a reader can tell a WEDGED writer (brain\n 1521│    // conn suspended) from a merely idle one and stall-evict it. Set BEFORE the write,\n 1522│    // cleared AFTER — the mutex is never held across `write_frame` (that would just\n     │...\n 3875│    /// surface a brain that never returned as a diagnosable fact, not a silent absence.\n*3876│    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 3877│    fn dispatch_stall_evicts(&self, send: &SharedSend) {\n 3878│        let (count, last_ms) = stall_evict_stats();\n 3879│        let frame = Envelope::new(\n     │...\n 4318│    /// replay it (the KNOWN-HAZARDS 7.41 control-steal class).\n*4319│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 4320│    fn dispatch_net_stream_retire(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4321│        let req: NetStreamRetireReq = serde_json::from_value(env.payload)\n 4322│            .map_err(|e| format!(\"bad net-stream-retire payload: {e}\"))?;\n     │...\n 4338│    /// classification fact the dispatcher reads instead of peeking ring seq 0.\n*4339│    // [impl->REQ-STREAM-OPENER-DURABLE]\n 4340│    fn dispatch_net_stream_opener(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {\n 4341│        let req: NetStreamOpenerReq = serde_json::from_value(env.payload)\n 4342│            .map_err(|e| format!(\"bad net-stream-opener payload: {e}\"))?;\n     │...\n 5818│\n*5819│    // [unit->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE] the pure stall predicate: a\n 5820│    // writer BLOCKED in a socket write past BRAIN_WRITE_DEADLINE is stalled; an idle\n 5821│    // writer (write_blocked_since None) and a just-started write are NOT — so a\n 5822│    // slow-but-draining brain never evicts, only a genuinely wedged one.\n### dispatch.rs\n  169│/// A claimed stream's dispatch lifecycle (ADR-0038 C,\n *170│/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n  171│/// insert performed BEFORE worker spawn and never cleared — one transient\n  172│/// worker-setup failure permanently abandoned the stream. Claims now\n  173│/// distinguish in-flight vs terminal vs retryable, and a transient failure\n     │...\n  200│/// off). The unit-tested seam for the no-hot-loop ruling.\n *201│// [impl->REQ-DISPATCH-CLAIM-RETRY]\n  202│fn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n  203│    match state {\n  204│        None => Some(0),\n     │...\n  213│/// spent, then goes terminal.\n *214│// [impl->REQ-DISPATCH-CLAIM-RETRY]\n  215│fn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\n  216│    match outcome {\n  217│        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n     │...\n  250│/// else = a pending request — serve it.\n *251│// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n  252│// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\n  253│fn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n  254│    finished && family == StreamFamily::Attach\n     │...\n  318│            // Terminal rows never re-enter; a released (Retry) claim re-enters\n *319│            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n  320│            if s.initiated_locally || s.next_seq == 0 {\n  321│                continue;\n  322│            }\n     │...\n  343│                if matches!(outcome, DispatchOutcome::Served(_)) {\n *344│                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n  345│                    // this stream's exchange is over — take its row out of\n  346│                    // every FUTURE dispatcher generation's enumeration (the\n  347│                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n     │...\n  388│/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\n *389│/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\n  390│/// it survives any ring roll — and fall back to the ring peek only when the\n  391│/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n  392│/// (terminal); `Err` = transient (the claim requeues bounded).\n     │...\n  576│                // gone, and a below-floor cursor trips the brain's gap check.\n *577│                // [impl->REQ-STREAM-OPENER-DURABLE]\n  578│                Some(endpoint) => match serve_attach(\n  579│                    &mut brain,\n  580│                    broker_name,\n     │...\n  959│\n *960│    // [unit->REQ-DISPATCH-CLAIM-RETRY] the claim lifecycle, at its pure seam:\n  961│    // a TRANSIENT failure releases the claim and requeues it after a backoff;\n  962│    // TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter\n  963│    // — a persistently-failing stream retries exactly its bounded budget and\n     │...\n 1002│\n*1003│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] the lifecycle+family\n 1004│    // discriminator (gate round 1): a finished ATTACH is terminal for\n 1005│    // redispatch (the operator detached — replay = the 7.41 controller\n 1006│    // steal), while a finished REQUEST/REPLY stream is NOT — sync and update\n### msg.rs\n 102│/// Brain→broker query: the brain-subscriber stall-evict tally (LIFECYCLE-TRUTH W2\n*103│/// observability rider, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) — how many\n 104│/// controllers were stall-evicted for a suspended/black-holed brain conn since\n 105│/// boot, and when the most recent fired. Surfaced on `daemon status` so a brain\n 106│/// that never returns is a diagnosable fact. An older broker predating this kind\n    │...\n 152│/// the caller treats retire as best-effort).\n*153│// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 154│pub const KIND_NET_STREAM_RETIRE: &str = \"net-stream-retire\";\n 155│/// Broker→brain: the retire ack (`removed` = a row actually left the table).\n 156│pub const KIND_NET_STREAM_RETIRED: &str = \"net-stream-retired\";\n    │...\n 160│/// answers `error` (KH-2.3: the dispatcher falls back to the ring peek).\n*161│// [impl->REQ-STREAM-OPENER-DURABLE]\n 162│pub const KIND_NET_STREAM_OPENER: &str = \"net-stream-opener\";\n 163│/// Broker→brain: the pinned opener reply (`opener_b64 = None` ⇒ not pinned:\n 164│/// first newline not yet arrived, or capture gave up bounded).\n    │...\n 570│\n*571│/// `stall-evicts-reply` payload (LIFECYCLE-TRUTH W2, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE):\n 572│/// the broker-global brain-subscriber stall-evict tally. Both fields defaulted so a\n 573│/// truncated/older reply still decodes (2.3 forward-compat).\n 574│#[derive(Debug, Clone, Serialize, Deserialize)]\n    │...\n 792│    /// broker omits it → `0`, today's subscribe-from-0 behavior.\n*793│    // [impl->REQ-STREAM-OPENER-DURABLE]\n 794│    #[serde(default)]\n 795│    pub floor_seq: u64,\n 796│}\n### nethost.rs\n  121│\n *122│/// Restart-durable opener capture (ADR-0038 B, REQ-STREAM-OPENER-DURABLE):\n  123│/// the stream's complete first NDJSON line, pinned OUTSIDE the evictable data\n  124│/// ring and held until the stream row goes away. Recovery classifies from\n  125│/// this fact, never from ring seq 0 (which a bounded ring evicts under\n     │...\n  254│        // `Oversize` are terminal states.\n *255│        // [impl->REQ-STREAM-OPENER-DURABLE]\n  256│        if let OpenerCapture::Buffering(buf) = &mut self.opener {\n  257│            match bytes.iter().position(|&b| b == b'\\n') {\n  258│                Some(nl) => {\n     │...\n  544│    /// is load-bearing; physical teardown belongs to the conn-close sweep only.\n *545│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n  546│    retired: std::sync::atomic::AtomicBool,\n  547│}\n  548│\n     │...\n  707│        // else reads a dead conn's stream ring.\n *708│        // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n  709│        sh_close\n  710│            .streams\n  711│            .lock()\n     │...\n 1560│    /// call newly retired the row (idempotent; absent row = false).\n*1561│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1562│    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1563│        let Some(entry) = self\n 1564│            .shared\n     │...\n 1587│    /// not yet arrived, or capture gave up bounded).\n*1588│    // [impl->REQ-STREAM-OPENER-DURABLE]\n 1589│    pub fn stream_opener(&self, stream_id: u64) -> Option<Option<Vec<u8>>> {\n 1590│        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\n 1591│        // A retired row answers no-such-stream: classification exists only to\n     │...\n 2053│\n*2054│    // [unit->REQ-STREAM-OPENER-DURABLE] the opener is pinned OUTSIDE the\n 2055│    // evictable ring: a small-cap log rolls its ring completely (floor\n 2056│    // advances past 0), yet the pinned first line — including one split\n 2057│    // across appends — survives verbatim. This is the fact recovery\n     │...\n 2078│\n*2079│    // [unit->REQ-STREAM-OPENER-DURABLE] capture is BOUNDED: a stream whose\n 2080│    // first bytes never carry a newline gives up at the cap (Oversize is\n 2081│    // terminal — a late newline cannot resurrect it), so a non-NDJSON stream\n 2082│    // costs one bounded buffer, never unbounded memory.\n     │...\n 2091│\n*2092│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement hides EXACTLY the\n 2093│    // named row from dispatch eligibility: the retired stream leaves the\n 2094│    // dispatch enumeration (stream_infos) while the active sibling stays\n 2095│    // claimable, a second retire is a no-op, and the pinned-opener query\n     │...\n 2130│\n*2131│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] retirement never severs the\n 2132│    // transport: a reply sent AFTER the row was retired still flushes to the\n 2133│    // peer. This is the field race's lost ordering made deterministic — the\n 2134│    // worker's reply send is fire-and-forget (brain returns before the broker\n     │...\n 2176│\n*2177│    // [unit->REQ-REDISPATCH-FINISHED-RETIRE] a dead CONNECTION retires its\n 2178│    // stream rows with it: the closed-watcher sweep leaves nothing for a\n 2179│    // future dispatcher generation to replay on a transport that can never\n 2180│    // serve again (the field bug's historical-rows-forever half).\n\n## tests/\n### brain_decouple.rs\n  *1│//! LIFECYCLE-TRUTH W2 (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) — the suspend-brain\n   2│//! int rig, on a REAL broker hosting a REAL PTY child over the real local IPC.\n   3│//!\n   4│//! ## The bug this gate locks down (the update-wedge root)\n    │...\n 219│\n*220│// [int->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n 221│#[test]\n 222│fn suspended_brain_controller_is_stall_evicted_take_completes_viewer_ticks() {\n 223│    init_home();","missingPaths":["requirements"],"linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784247358982}}
{"type":"message","id":"585c7a50","parentId":"7545c465","timestamp":"2026-07-17T00:16:01.009Z","message":{"role":"toolResult","toolCallId":"call_Z67AgtLhFMjr2EtMQkC5KxVc|fc_099a7567dadef391016a59743edf64819b9fc53da03ac5edab","toolName":"glob","content":[{"type":"text","text":"Cargo.toml\ntraceable-reqs.toml\n# target/debug/\n## .fingerprint/\n### reqwest-ac75cf070f34b907/\nlib-reqwest\nlib-reqwest.json\ndep-lib-reqwest\n### reqwest-6b5a94550f6dc659/\nlib-reqwest.json\nlib-reqwest\ndep-lib-reqwest\n### reqwest-573a35dd52b00aaa/\nlib-reqwest\nlib-reqwest.json\ndep-lib-reqwest\n### reqwest-17cb2a5694bd2db8/\nlib-reqwest.json\nlib-reqwest\ndep-lib-reqwest\n## deps/\nlibreqwest-ac75cf070f34b907.rlib\nlibreqwest-ac75cf070f34b907.rmeta\nreqwest-ac75cf070f34b907.d\nreqwest-6b5a94550f6dc659.d\nlibreqwest-6b5a94550f6dc659.rmeta\nreqwest-573a35dd52b00aaa.d\nlibreqwest-573a35dd52b00aaa.rmeta\nlibreqwest-17cb2a5694bd2db8.rlib\nlibreqwest-17cb2a5694bd2db8.rmeta\nreqwest-17cb2a5694bd2db8.d\n## incremental/\n### idempotent-31j5k8fm1ian5/\ns-hkgm7eei2j-0kbreqr.lock\n#### s-hkgm7eei2j-0kbreqr-d9u0l9wezcls705t3khyv2ixc/\n### psyche_context_file_e2e-2dqrb136ok7yi/s-hkglrh8sl6-0vpv7rr-dreqqj75ul14od8on9hlgvak0/\n### control_stamp_lifetime-2g8xrd5s2zzcg/\n#### s-hkglsjmrbq-0iryj36-65n0e2gs1qvewlmzm025dd0ju/\nakuboki46oreqkv9052w0rsat.o\n#### s-hkgm8242u0-0m58x17-a4wp2rh6hdl2yz11bonqsynl5/\nakuboki46oreqkv9052w0rsat.o\n### xlate_choreo_fixture-1c2lrtygf8rg8/\n#### s-hkglsp4uww-0tu2p1j-14a4aab8pm26q4m78qeestim0/\n2rg6ibrreqf3skdnawsurssc3.o\n#### s-hkgm7j8aj9-1byibjx-14a4aab8pm26q4m78qeestim0/\n2rg6ibrreqf3skdnawsurssc3.o\n### spt_msg-03o7jy94r2fvm/\n#### s-hkg76901ul-1ptx13a-7rcqkfm8ynefcue6cxeff73vr/\nb3bqreql5j9g4bj5vj76hyipo.o\n#### s-hkgkqln9nm-156q7m7-clf2ornzzk8871l3wi7bm6bmg/\nb3bqreql5j9g4bj5vj76hyipo.o\n## build/\n### aws-lc-sys-66e6bfff003e2083/out/\n01ff1b89ed0b6d38-x_req.o\n01ff1b89ed0b6d38-x509_req.o\n01ff1b89ed0b6d38-t_req.o\n### aws-lc-sys-1a05d93e0719da20/out/\n01ff1b89ed0b6d38-x_req.o\n01ff1b89ed0b6d38-x509_req.o\n01ff1b89ed0b6d38-t_req.o\n# .git/\npacked-refs\n## logs/refs/\n### heads/\nmain\nuwedge2-resume-viewer\nupdate-wedge-drain-drive\nf-035-window-drain-honor\npump-w2-design\npump-w1-fix\nmusl-w2\nmusl-w1\ngate-inject-rearm\nlifecycle-truth\nworker-truth\nremote-truth\nf030-psyche-ephemeral\nf026-pickertruth\nf025-listredesign\nf023-wan-idle-delivery\nboundary-rotation-credential\nendpoint-list-redesign\ncounter39-field-bugs\nfield-bug-milestone\nbug2-offline-ttl\nbug6-screen-grid\nbug4-cross-node-attach\nwedge-trace-v4\nwedge-trace-v3\nwedge-trace-v2\nwedge-trace-instrumentation\nviewer-drain-decouple-b4\nv0.9.1-resolve-dotstem\nv0.9.1-picker-status\nv0.9.0-harness-resolution\nv0.8.3-broker-quic-deadline\nv0.8.2-templating-residency\nv0.8.1-livehost-bootrace\nv0.17.0-robust-join\nv0.16.0-update-arc\nv0.15.0-messaging\nv0.13.2-adapter-packaging\nv0.13.0-p1b-ack-deadlock\nv0.13.0-delivery-control\nv0.12.1-lifecycle\nv0.12.1-help-md\nv0.12.0-lifecycle\nv0.11.0-findings\nv0.10.0-picker-status\nupdate-fetch-ux\nsimple-fixes\npump-carrier-fix\nm12-w1-bringup-rc\nm11-shell-substrate\nm10-digest-extractor\ndoyle-work\ndev-freeform\n#### release/\n#### fix/\n#### docs/\n#### triage/\n#### test/\n#### release-merge/\n#### spec/\n#### rca/\n#### feat/\n#### design/\n### remotes/\n#### origin/\n#### mirror/\n## refs/tags/\nv0.34.0\nv0.33.0\nv0.32.0\nv0.31.0\nv0.30.6\nv0.30.5\nv0.30.4\nv0.30.3\nv0.30.2\nv0.30.1\nv0.30.0\nv0.29.1\nv0.29.0\nv0.28.0\nv0.27.0\nv0.26.0\nv0.25.0\nv0.24.0\nv0.23.0\nv0.22.0\nv0.21.0\nv0.20.0\nv0.19.1\nv0.19.0\n# crates/\n## spt-daemon/\nCargo.toml\n## spt/\nCargo.toml\n## spt-store/\nCargo.toml\n## spt-net/\nCargo.toml\n## xtask/\nCargo.toml\n## spt-term/\nCargo.toml\n## spt-runtime/\nCargo.toml\n## spt-proto/\nCargo.toml\n## spt-msg/\nCargo.toml\n## spt-live/\nCargo.toml\n# .worktrees/\n## gate-8981d81/\ntraceable-reqs.toml\nREQ-MSG-ENVELOPE-PLAN.md\n### .gate-target/debug/\n#### .fingerprint/reqwest-573a35dd52b00aaa/\nlib-reqwest.json\nlib-reqwest\ndep-lib-reqwest\n#### deps/\nreqwest-573a35dd52b00aaa.d\nlibreqwest-573a35dd52b00aaa.rmeta\n#### build/aws-lc-sys-66e6bfff003e2083/out/\n01ff1b89ed0b6d38-x_req.o\n01ff1b89ed0b6d38-x509_req.o\n01ff1b89ed0b6d38-t_req.o\n## r4-stale-guard/\ntraceable-reqs.toml\nREQ-MSG-ENVELOPE-PLAN.md\n### target/debug/\n#### .fingerprint/reqwest-6b5a94550f6dc659/\nlib-reqwest.json\nlib-reqwest\ndep-lib-reqwest\n#### deps/\nreqwest-6b5a94550f6dc659.d\nlibreqwest-6b5a94550f6dc659.rmeta\n## rca-capture/\ntraceable-reqs.toml\nREQ-MSG-ENVELOPE-PLAN.md\n## f-035/\ntraceable-reqs.toml\nREQ-MSG-ENVELOPE-PLAN.md\n## spec-pilot/\ntraceable-reqs.toml\n### docs/plans/\nREQ-MSG-ENVELOPE-PLAN.md\n## docs-reorg/\ntraceable-reqs.toml\n### docs/plans/\nREQ-MSG-ENVELOPE-PLAN.md\n### target/debug/\n#### .fingerprint/reqwest-6b5a94550f6dc659/\nlib-reqwest\nlib-reqwest.json\ndep-lib-reqwest\n#### deps/\nreqwest-6b5a94550f6dc659.d\nlibreqwest-6b5a94550f6dc659.rmeta\n# target-seam/debug/\n## incremental/spt_daemon-0nwu5ggphnr9v/\n### s-hjltpnq3uc-1hxcbta-1yvu6lvva4ua71k85tnq89cwy/\n0vobjkuz3q46ovjzg1dlkreqg.o\n### s-hjlucsmvm1-13phy1f-f3gzswttisc5juzw21iaeufj9/\n0vobjkuz3q46ovjzg1dlkreqg.o\n## .fingerprint/\n### reqwest-17cb2a5694bd2db8/\nlib-reqwest.json\nlib-reqwest\ndep-lib-reqwest\n### reqwest-573a35dd52b00aaa/\nlib-reqwest.json\nlib-reqwest\ndep-lib-reqwest\n### reqwest-ac75cf070f34b907/\nlib-reqwest.json\nlib-reqwest\n## deps/\nlibreqwest-17cb2a5694bd2db8.rlib\nlibreqwest-17cb2a5694bd2db8.rmeta\nreqwest-17cb2a5694bd2db8.d\nreqwest-573a35dd52b00aaa.d\nlibreqwest-573a35dd52b00aaa.rmeta\nlibreqwest-ac75cf070f34b907.rlib\nlibreqwest-ac75cf070f34b907.rmeta\nreqwest-ac75cf070f34b907.d\n## build/aws-lc-sys-66e6bfff003e2083/out/\n01ff1b89ed0b6d38-x_req.o\n01ff1b89ed0b6d38-x509_req.o\n01ff1b89ed0b6d38-t_req.o\n\n[200 results limit reached. Use limit=400 for more]"}],"details":{"scopePath":"*req*, **/*req*, **/*REQ*, traceable-reqs.toml, Cargo.toml, crates/*/Cargo.toml, .git/refs/tags/*, .git/logs/refs/*/*, .git/packed-refs","fileCount":200,"files":["target/debug/.fingerprint/reqwest-ac75cf070f34b907/lib-reqwest","target/debug/.fingerprint/reqwest-ac75cf070f34b907/lib-reqwest.json","target/debug/deps/libreqwest-ac75cf070f34b907.rlib","target/debug/deps/libreqwest-ac75cf070f34b907.rmeta","target/debug/deps/reqwest-ac75cf070f34b907.d","target/debug/.fingerprint/reqwest-ac75cf070f34b907/dep-lib-reqwest","Cargo.toml",".git/logs/refs/heads/release/",".git/packed-refs",".git/logs/refs/heads/main","traceable-reqs.toml","target/debug/.fingerprint/reqwest-6b5a94550f6dc659/lib-reqwest.json","target/debug/.fingerprint/reqwest-6b5a94550f6dc659/","target/debug/.fingerprint/reqwest-6b5a94550f6dc659/lib-reqwest","target/debug/deps/reqwest-6b5a94550f6dc659.d","target/debug/.fingerprint/reqwest-6b5a94550f6dc659/dep-lib-reqwest","target/debug/deps/libreqwest-6b5a94550f6dc659.rmeta","target/debug/incremental/idempotent-31j5k8fm1ian5/s-hkgm7eei2j-0kbreqr-d9u0l9wezcls705t3khyv2ixc/","target/debug/incremental/idempotent-31j5k8fm1ian5/s-hkgm7eei2j-0kbreqr.lock","target/debug/incremental/psyche_context_file_e2e-2dqrb136ok7yi/s-hkglrh8sl6-0vpv7rr-dreqqj75ul14od8on9hlgvak0/","target/debug/.fingerprint/reqwest-573a35dd52b00aaa/","target/debug/.fingerprint/reqwest-573a35dd52b00aaa/lib-reqwest","target/debug/.fingerprint/reqwest-573a35dd52b00aaa/lib-reqwest.json","target/debug/deps/reqwest-573a35dd52b00aaa.d","target/debug/.fingerprint/reqwest-573a35dd52b00aaa/dep-lib-reqwest","target/debug/deps/libreqwest-573a35dd52b00aaa.rmeta","target/debug/build/aws-lc-sys-66e6bfff003e2083/out/01ff1b89ed0b6d38-x_req.o","target/debug/build/aws-lc-sys-66e6bfff003e2083/out/01ff1b89ed0b6d38-x509_req.o","target/debug/build/aws-lc-sys-66e6bfff003e2083/out/01ff1b89ed0b6d38-t_req.o","target/debug/.fingerprint/reqwest-ac75cf070f34b907/","target/debug/build/aws-lc-sys-1a05d93e0719da20/out/01ff1b89ed0b6d38-x_req.o","target/debug/build/aws-lc-sys-1a05d93e0719da20/out/01ff1b89ed0b6d38-x509_req.o","target/debug/build/aws-lc-sys-1a05d93e0719da20/out/01ff1b89ed0b6d38-t_req.o","target/debug/incremental/control_stamp_lifetime-2g8xrd5s2zzcg/s-hkglsjmrbq-0iryj36-65n0e2gs1qvewlmzm025dd0ju/akuboki46oreqkv9052w0rsat.o","target/debug/incremental/control_stamp_lifetime-2g8xrd5s2zzcg/s-hkgm8242u0-0m58x17-a4wp2rh6hdl2yz11bonqsynl5/akuboki46oreqkv9052w0rsat.o","target/debug/incremental/xlate_choreo_fixture-1c2lrtygf8rg8/s-hkglsp4uww-0tu2p1j-14a4aab8pm26q4m78qeestim0/2rg6ibrreqf3skdnawsurssc3.o","target/debug/incremental/xlate_choreo_fixture-1c2lrtygf8rg8/s-hkgm7j8aj9-1byibjx-14a4aab8pm26q4m78qeestim0/2rg6ibrreqf3skdnawsurssc3.o",".git/logs/refs/heads/fix/","target/debug/.fingerprint/reqwest-17cb2a5694bd2db8/","target/debug/.fingerprint/reqwest-17cb2a5694bd2db8/lib-reqwest.json","target/debug/.fingerprint/reqwest-17cb2a5694bd2db8/lib-reqwest","target/debug/deps/libreqwest-17cb2a5694bd2db8.rlib","target/debug/deps/libreqwest-17cb2a5694bd2db8.rmeta","target/debug/deps/reqwest-17cb2a5694bd2db8.d","target/debug/.fingerprint/reqwest-17cb2a5694bd2db8/dep-lib-reqwest","target/debug/incremental/spt_msg-03o7jy94r2fvm/s-hkg76901ul-1ptx13a-7rcqkfm8ynefcue6cxeff73vr/b3bqreql5j9g4bj5vj76hyipo.o","target/debug/incremental/spt_msg-03o7jy94r2fvm/s-hkgkqln9nm-156q7m7-clf2ornzzk8871l3wi7bm6bmg/b3bqreql5j9g4bj5vj76hyipo.o",".git/refs/tags/v0.34.0","crates/spt-daemon/Cargo.toml",".git/refs/tags/v0.33.0",".git/logs/refs/heads/docs/",".git/logs/refs/remotes/origin/","crates/spt/Cargo.toml",".git/refs/tags/v0.32.0",".worktrees/gate-8981d81/.gate-target/debug/.fingerprint/reqwest-573a35dd52b00aaa/",".worktrees/gate-8981d81/.gate-target/debug/.fingerprint/reqwest-573a35dd52b00aaa/lib-reqwest.json",".worktrees/gate-8981d81/.gate-target/debug/.fingerprint/reqwest-573a35dd52b00aaa/lib-reqwest",".worktrees/gate-8981d81/.gate-target/debug/deps/reqwest-573a35dd52b00aaa.d",".worktrees/gate-8981d81/.gate-target/debug/.fingerprint/reqwest-573a35dd52b00aaa/dep-lib-reqwest",".worktrees/gate-8981d81/.gate-target/debug/deps/libreqwest-573a35dd52b00aaa.rmeta",".worktrees/gate-8981d81/.gate-target/debug/build/aws-lc-sys-66e6bfff003e2083/out/01ff1b89ed0b6d38-x_req.o",".worktrees/gate-8981d81/.gate-target/debug/build/aws-lc-sys-66e6bfff003e2083/out/01ff1b89ed0b6d38-x509_req.o",".worktrees/gate-8981d81/.gate-target/debug/build/aws-lc-sys-66e6bfff003e2083/out/01ff1b89ed0b6d38-t_req.o",".worktrees/gate-8981d81/traceable-reqs.toml",".worktrees/gate-8981d81/REQ-MSG-ENVELOPE-PLAN.md",".git/logs/refs/remotes/mirror/",".git/refs/tags/v0.31.0",".git/logs/refs/heads/triage/","crates/spt-store/Cargo.toml",".git/refs/tags/v0.30.6",".worktrees/r4-stale-guard/target/debug/.fingerprint/reqwest-6b5a94550f6dc659/",".worktrees/r4-stale-guard/target/debug/.fingerprint/reqwest-6b5a94550f6dc659/lib-reqwest.json",".worktrees/r4-stale-guard/target/debug/.fingerprint/reqwest-6b5a94550f6dc659/lib-reqwest",".worktrees/r4-stale-guard/target/debug/deps/reqwest-6b5a94550f6dc659.d",".worktrees/r4-stale-guard/target/debug/.fingerprint/reqwest-6b5a94550f6dc659/dep-lib-reqwest",".worktrees/r4-stale-guard/target/debug/deps/libreqwest-6b5a94550f6dc659.rmeta",".worktrees/r4-stale-guard/traceable-reqs.toml",".worktrees/r4-stale-guard/REQ-MSG-ENVELOPE-PLAN.md",".git/logs/refs/heads/test/",".git/logs/refs/heads/release-merge/",".worktrees/rca-capture/traceable-reqs.toml",".worktrees/rca-capture/REQ-MSG-ENVELOPE-PLAN.md",".git/refs/tags/v0.30.5",".git/logs/refs/heads/uwedge2-resume-viewer",".git/refs/tags/v0.30.4",".git/refs/tags/v0.30.3",".git/logs/refs/heads/update-wedge-drain-drive",".git/logs/refs/heads/f-035-window-drain-honor",".worktrees/f-035/traceable-reqs.toml",".worktrees/f-035/REQ-MSG-ENVELOPE-PLAN.md",".git/refs/tags/v0.30.2",".git/logs/refs/heads/pump-w2-design",".worktrees/spec-pilot/traceable-reqs.toml",".git/refs/tags/v0.30.1",".git/logs/refs/heads/pump-w1-fix",".worktrees/spec-pilot/docs/plans/REQ-MSG-ENVELOPE-PLAN.md",".git/logs/refs/heads/spec/",".worktrees/docs-reorg/traceable-reqs.toml",".worktrees/docs-reorg/docs/plans/REQ-MSG-ENVELOPE-PLAN.md",".worktrees/docs-reorg/target/debug/.fingerprint/reqwest-6b5a94550f6dc659/",".worktrees/docs-reorg/target/debug/.fingerprint/reqwest-6b5a94550f6dc659/lib-reqwest",".worktrees/docs-reorg/target/debug/.fingerprint/reqwest-6b5a94550f6dc659/lib-reqwest.json",".worktrees/docs-reorg/target/debug/deps/reqwest-6b5a94550f6dc659.d",".worktrees/docs-reorg/target/debug/.fingerprint/reqwest-6b5a94550f6dc659/dep-lib-reqwest",".worktrees/docs-reorg/target/debug/deps/libreqwest-6b5a94550f6dc659.rmeta",".git/refs/tags/v0.30.0",".git/logs/refs/heads/rca/","crates/spt-net/Cargo.toml",".git/logs/refs/heads/feat/",".git/logs/refs/heads/musl-w2",".git/logs/refs/heads/musl-w1",".git/refs/tags/v0.29.1",".git/logs/refs/heads/gate-inject-rearm",".git/logs/refs/heads/lifecycle-truth",".git/refs/tags/v0.29.0",".git/refs/tags/v0.28.0",".git/refs/tags/v0.27.0",".git/logs/refs/heads/worker-truth",".git/refs/tags/v0.26.0",".git/logs/refs/heads/remote-truth","crates/xtask/Cargo.toml","crates/spt-term/Cargo.toml","crates/spt-runtime/Cargo.toml","crates/spt-proto/Cargo.toml","crates/spt-msg/Cargo.toml","crates/spt-live/Cargo.toml",".git/logs/refs/heads/f030-psyche-ephemeral",".git/refs/tags/v0.25.0",".git/refs/tags/v0.24.0",".git/refs/tags/v0.23.0",".git/refs/tags/v0.22.0",".git/logs/refs/heads/f026-pickertruth",".git/refs/tags/v0.21.0",".git/logs/refs/heads/f025-listredesign",".git/refs/tags/v0.20.0",".git/logs/refs/heads/f023-wan-idle-delivery",".git/logs/refs/heads/boundary-rotation-credential",".git/logs/refs/heads/endpoint-list-redesign",".git/refs/tags/v0.19.1",".git/logs/refs/heads/counter39-field-bugs",".git/refs/tags/v0.19.0",".git/logs/refs/heads/field-bug-milestone",".git/logs/refs/heads/bug2-offline-ttl",".git/logs/refs/heads/bug6-screen-grid",".git/logs/refs/heads/bug4-cross-node-attach",".git/logs/refs/heads/wedge-trace-v4",".git/logs/refs/heads/wedge-trace-v3",".git/logs/refs/heads/wedge-trace-v2",".git/logs/refs/heads/wedge-trace-instrumentation",".git/logs/refs/heads/viewer-drain-decouple-b4",".git/logs/refs/heads/v0.9.1-resolve-dotstem",".git/logs/refs/heads/v0.9.1-picker-status",".git/logs/refs/heads/v0.9.0-harness-resolution",".git/logs/refs/heads/v0.8.3-broker-quic-deadline",".git/logs/refs/heads/v0.8.2-templating-residency",".git/logs/refs/heads/v0.8.1-livehost-bootrace",".git/logs/refs/heads/v0.17.0-robust-join",".git/logs/refs/heads/v0.16.0-update-arc",".git/logs/refs/heads/v0.15.0-messaging",".git/logs/refs/heads/v0.13.2-adapter-packaging",".git/logs/refs/heads/v0.13.0-p1b-ack-deadlock",".git/logs/refs/heads/v0.13.0-delivery-control",".git/logs/refs/heads/v0.12.1-lifecycle",".git/logs/refs/heads/v0.12.1-help-md",".git/logs/refs/heads/v0.12.0-lifecycle",".git/logs/refs/heads/v0.11.0-findings",".git/logs/refs/heads/v0.10.0-picker-status",".git/logs/refs/heads/update-fetch-ux",".git/logs/refs/heads/simple-fixes",".git/logs/refs/heads/pump-carrier-fix",".git/logs/refs/heads/m12-w1-bringup-rc",".git/logs/refs/heads/m11-shell-substrate",".git/logs/refs/heads/m10-digest-extractor",".git/logs/refs/heads/doyle-work",".git/logs/refs/heads/dev-freeform",".git/logs/refs/heads/design/","target-seam/debug/incremental/spt_daemon-0nwu5ggphnr9v/s-hjltpnq3uc-1hxcbta-1yvu6lvva4ua71k85tnq89cwy/0vobjkuz3q46ovjzg1dlkreqg.o","target-seam/debug/incremental/spt_daemon-0nwu5ggphnr9v/s-hjlucsmvm1-13phy1f-f3gzswttisc5juzw21iaeufj9/0vobjkuz3q46ovjzg1dlkreqg.o","target-seam/debug/.fingerprint/reqwest-17cb2a5694bd2db8/","target-seam/debug/.fingerprint/reqwest-17cb2a5694bd2db8/lib-reqwest.json","target-seam/debug/.fingerprint/reqwest-17cb2a5694bd2db8/lib-reqwest","target-seam/debug/deps/libreqwest-17cb2a5694bd2db8.rlib","target-seam/debug/deps/libreqwest-17cb2a5694bd2db8.rmeta","target-seam/debug/deps/reqwest-17cb2a5694bd2db8.d","target-seam/debug/.fingerprint/reqwest-17cb2a5694bd2db8/dep-lib-reqwest","target-seam/debug/.fingerprint/reqwest-573a35dd52b00aaa/","target-seam/debug/.fingerprint/reqwest-573a35dd52b00aaa/lib-reqwest.json","target-seam/debug/.fingerprint/reqwest-573a35dd52b00aaa/lib-reqwest","target-seam/debug/deps/reqwest-573a35dd52b00aaa.d","target-seam/debug/.fingerprint/reqwest-573a35dd52b00aaa/dep-lib-reqwest","target-seam/debug/deps/libreqwest-573a35dd52b00aaa.rmeta","target-seam/debug/build/aws-lc-sys-66e6bfff003e2083/out/01ff1b89ed0b6d38-x_req.o","target-seam/debug/build/aws-lc-sys-66e6bfff003e2083/out/01ff1b89ed0b6d38-x509_req.o","target-seam/debug/build/aws-lc-sys-66e6bfff003e2083/out/01ff1b89ed0b6d38-t_req.o","target-seam/debug/.fingerprint/reqwest-ac75cf070f34b907/","target-seam/debug/.fingerprint/reqwest-ac75cf070f34b907/lib-reqwest.json","target-seam/debug/.fingerprint/reqwest-ac75cf070f34b907/lib-reqwest","target-seam/debug/deps/libreqwest-ac75cf070f34b907.rlib","target-seam/debug/deps/libreqwest-ac75cf070f34b907.rmeta","target-seam/debug/deps/reqwest-ac75cf070f34b907.d"],"truncated":true,"resultLimitReached":200,"cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","meta":{"limits":{"resultLimit":{"reached":200,"suggestion":400}}}},"isError":false,"timestamp":1784247361009}}
{"type":"message","id":"bdaa5e57","parentId":"585c7a50","timestamp":"2026-07-17T00:16:10.236Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Reading release tag hashes**"},{"type":"toolCall","id":"call_BAbYAb8tkkFBjpSTxtKkA4Wt|fc_099a7567dadef391016a59744a30c4819ba761b97588dc2a9f","name":"read","arguments":{"i":"Reading release tag target","path":".git/refs/tags/v0.34.0","selector":"raw"}},{"type":"toolCall","id":"call_p3hHPVoJ07g47HScLVhfjUXX|fc_099a7567dadef391016a59744a30d4819b9cc796520014471e","name":"read","arguments":{"i":"Reading prior tag target","path":".git/refs/tags/v0.33.0","selector":"raw"}},{"type":"toolCall","id":"call_yqoceELYGyWfa12cH7JqcfXH|fc_099a7567dadef391016a59744a30f4819b90fe39370c32b498","name":"read","arguments":{"i":"Reading main reflog tail","path":".git/logs/refs/heads/main","selector":"120-"}},{"type":"toolCall","id":"call_jtkY8OTISQni8V6QlwiAjlRC|fc_099a7567dadef391016a59744a30f8819b8dc71f130322142f","name":"read","arguments":{"i":"Reading packed release refs","path":".git/packed-refs","selector":"raw"}},{"type":"toolCall","id":"call_VmOpF2ahnneopSywQYepVfIh|fc_099a7567dadef391016a59744a3100819b9fcae2429cbb2fd5","name":"read","arguments":{"i":"Reading workspace version","path":"Cargo.toml","selector":"1-30"}},{"type":"toolCall","id":"call_5bLc29hcmrAkvOXsliySdenI|fc_099a7567dadef391016a59744a3104819b881d42ed2e89666b","name":"read","arguments":{"i":"Reading workflow note extraction","path":".github/workflows/release.yml","selector":"1-180"}},{"type":"toolCall","id":"call_XSQ8A4c49Epbb1qlL3LAXMsQ|fc_099a7567dadef391016a59744a3108819b9d93366535a24fa0","name":"grep","arguments":{"i":"Finding release branch history","pattern":"0\\.34\\.0|redispatch|release.*34|release:|v0\\.34","path":".git/logs/refs; .git/logs/HEAD","selector":null,"case":false,"gitignore":false,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":19441,"output":318,"cacheRead":37376,"cacheWrite":0,"totalTokens":57135,"reasoningTokens":31,"cost":{"input":0.09720500000000001,"output":0.00954,"cacheRead":0.018688,"cacheWrite":0,"total":0.12543300000000002}},"stopReason":"toolUse","timestamp":1784247361095,"responseId":"resp_099a7567dadef391016a5974413f64819b99dddb3e32b6d648","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a59744476b0819b91f8b34ce58729ad","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXRKjkuLH-X6yWd9FKn0WEqSDFbuqSTrp_r56mVW4pv_9ZGCpDTDqcv3-Ge-titEe6rDGDYWwXd8a41vCe9iZeyJwTXuSAgMHu2q80T5JNU3vE1yZ-31Qqo1-5NpS1qmMPNnOYtzA2PnL2GuaIqd6vjIFww1pkzv9X2O7APGWV9DlySnF0tgYn3PPKXRwFaqTjw2dXjRzA94PU1GC5-GkB1YDuUR94RCeINQo1nw_eMBJequRYVJwfQvzH4Y7mTckd3tVKB0laARGUX5QMP-WiiTHVINWx_peNc_nEOA_ry1gij3V51jhu4uwjaV-aIJ2Mps0S2fGl6-A3sxSw32CsQYsSVEVTQiqeQFvuxM2eW4Z0TjIc1P09pbqemMsKKED2Jvap-vE-lxDgGLzGuQIVuu2ptH2T-nIeqEdU27MOPL2mYphObBxwOVH9t6ATgH1e6KJFoLt5w4EV-pYfDE2E8oE3-rz28iR1FBvbiRQSa-o8xkqAuVFKpvX6DuxFx4jOPfqBmcDI6YuNGj59CsVa8z0ObOSTb3CxV-SRc4ywbGub7OiLh2P0mnY6nbP1xpNl6-boFXwzDZy-G0ZsL1zhYs4BBQKBrePg6vXVwqPTQ3O4t35Fx4GXOiSjSpf6VGS4aOYzq5a9UW3qqvsqC51xdnZEleW6OMocP2EVgqzLvJrmOIz09PQxFbpV1Ji0X_jVts-ATG-aFB4g0VeWumPueAyg2YyapOYdHYOSMK2mG-dQdjbRRbNg283Q8-VoBOltjPFqeZnI9MGq_JJczGCbXiQ1S0HtgTOYObzBqEOFmfHc6ZA35B1e5L5Ia-llxQS42oBjoN5riAYgVh81Y0gKKlrPj3jbU5WNdDBSeRQHM3a1RhPDUF3jqXYssZPKAgaMdtNk5LnwRI94ZCnbBwuLqDErSAAiAYxtWuL_KUClK9TWt2GZnxkga9iI4BrbF5L3UmuLbO-UfQS5iReFs4gfQLHiuk1dTtreL663RWF8iVgabIdUwXSyKzq2FuUcsSz2d3cWvZzoQC5J39uf2Kmi3pskiSCC3a7RzQozzoHjLkE4mre5UBpHQ7bZNapuObB5Eh","summary":[{"type":"summary_text","text":"**Reading release tag hashes**"}]},{"id":"fc_099a7567dadef391016a59744a30c4819ba761b97588dc2a9f","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading release tag target\",\"path\":\".git/refs/tags/v0.34.0\",\"selector\":\"raw\"}","call_id":"call_BAbYAb8tkkFBjpSTxtKkA4Wt","name":"read"},{"id":"fc_099a7567dadef391016a59744a30d4819b9cc796520014471e","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading prior tag target\",\"path\":\".git/refs/tags/v0.33.0\",\"selector\":\"raw\"}","call_id":"call_p3hHPVoJ07g47HScLVhfjUXX","name":"read"},{"id":"fc_099a7567dadef391016a59744a30f4819b90fe39370c32b498","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading main reflog tail\",\"path\":\".git/logs/refs/heads/main\",\"selector\":\"120-\"}","call_id":"call_yqoceELYGyWfa12cH7JqcfXH","name":"read"},{"id":"fc_099a7567dadef391016a59744a30f8819b8dc71f130322142f","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading packed release refs\",\"path\":\".git/packed-refs\",\"selector\":\"raw\"}","call_id":"call_jtkY8OTISQni8V6QlwiAjlRC","name":"read"},{"id":"fc_099a7567dadef391016a59744a3100819b9fcae2429cbb2fd5","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading workspace version\",\"path\":\"Cargo.toml\",\"selector\":\"1-30\"}","call_id":"call_VmOpF2ahnneopSywQYepVfIh","name":"read"},{"id":"fc_099a7567dadef391016a59744a3104819b881d42ed2e89666b","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading workflow note extraction\",\"path\":\".github/workflows/release.yml\",\"selector\":\"1-180\"}","call_id":"call_5bLc29hcmrAkvOXsliySdenI","name":"read"},{"id":"fc_099a7567dadef391016a59744a3108819b9d93366535a24fa0","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding release branch history\",\"pattern\":\"0\\\\.34\\\\.0|redispatch|release.*34|release:|v0\\\\.34\",\"path\":\".git/logs/refs; .git/logs/HEAD\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_XSQ8A4c49Epbb1qlL3LAXMsQ","name":"grep"}]},"duration":9141.192700006068,"ttft":3359.0950000062585,"contextSnapshot":{"promptTokens":56817,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_BAbYAb8tkkFBjpSTxtKkA4Wt|fc_099a7567dadef391016a59744a30c4819ba761b97588dc2a9f","toolName":"read","startedAt":"2026-07-17T00:16:10.237Z","args":{"path":".git/refs/tags/v0.34.0"},"intent":"Reading release tag target"},"id":"920ca866","parentId":"bdaa5e57","timestamp":"2026-07-17T00:16:10.237Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_p3hHPVoJ07g47HScLVhfjUXX|fc_099a7567dadef391016a59744a30d4819b9cc796520014471e","toolName":"read","startedAt":"2026-07-17T00:16:10.237Z","args":{"path":".git/refs/tags/v0.33.0"},"intent":"Reading prior tag target"},"id":"82cf58ab","parentId":"920ca866","timestamp":"2026-07-17T00:16:10.237Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_yqoceELYGyWfa12cH7JqcfXH|fc_099a7567dadef391016a59744a30f4819b90fe39370c32b498","toolName":"read","startedAt":"2026-07-17T00:16:10.237Z","args":{"path":".git/logs/refs/heads/main"},"intent":"Reading main reflog tail"},"id":"c8f87c29","parentId":"82cf58ab","timestamp":"2026-07-17T00:16:10.237Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_jtkY8OTISQni8V6QlwiAjlRC|fc_099a7567dadef391016a59744a30f8819b8dc71f130322142f","toolName":"read","startedAt":"2026-07-17T00:16:10.237Z","args":{"path":".git/packed-refs"},"intent":"Reading packed release refs"},"id":"b9f67153","parentId":"c8f87c29","timestamp":"2026-07-17T00:16:10.237Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_VmOpF2ahnneopSywQYepVfIh|fc_099a7567dadef391016a59744a3100819b9fcae2429cbb2fd5","toolName":"read","startedAt":"2026-07-17T00:16:10.237Z","args":{"path":"Cargo.toml"},"intent":"Reading workspace version"},"id":"2f156d84","parentId":"b9f67153","timestamp":"2026-07-17T00:16:10.237Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_5bLc29hcmrAkvOXsliySdenI|fc_099a7567dadef391016a59744a3104819b881d42ed2e89666b","toolName":"read","startedAt":"2026-07-17T00:16:10.237Z","args":{"path":".github/workflows/release.yml"},"intent":"Reading workflow note extraction"},"id":"5d9df4d4","parentId":"2f156d84","timestamp":"2026-07-17T00:16:10.237Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_XSQ8A4c49Epbb1qlL3LAXMsQ|fc_099a7567dadef391016a59744a3108819b9d93366535a24fa0","toolName":"grep","startedAt":"2026-07-17T00:16:10.237Z","args":{"path":".git/logs/refs; .git/logs/HEAD"},"intent":"Finding release branch history"},"id":"aca94ca8","parentId":"5d9df4d4","timestamp":"2026-07-17T00:16:10.237Z"}
{"type":"message","id":"34a96a28","parentId":"aca94ca8","timestamp":"2026-07-17T00:16:10.239Z","message":{"role":"toolResult","toolCallId":"call_jtkY8OTISQni8V6QlwiAjlRC|fc_099a7567dadef391016a59744a30f8819b8dc71f130322142f","toolName":"read","content":[{"type":"text","text":"# pack-refs with: peeled fully-peeled sorted \n1941eb823b36cc6ecc95dcbffe49b5a2047335fd refs/heads/design/endpoint-creation-flow\nc5887db27f6c8576decec071c073e76c5144ba66 refs/heads/dev-freeform\nf747e1e7048831b51c8aed18a611d10238c3eda4 refs/heads/docs/msg-envelope-adr-0020\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/doyle-work\naa5fa2a42e7ab13045d1827eefcddade797bac73 refs/heads/feat/translate-proof\n3da4aa269253b7b8bf0b3c9dca31c6e5ee2f51d1 refs/heads/field-bug-milestone\nb4275c3c4fcce720bf244b32f42a34b18d419443 refs/heads/fix/digest-proof-session-id-keys\n4fd028d5ceda811557f361162f50987db929eb02 refs/heads/fix/f019-translation-install-dir-resolve\n60fadd25f3b62712607822692d4b7596b4a68de3 refs/heads/fix/v0143-raw-inject-removal\nbe3d207723b2e59b01bbbc58151221af0504c58f refs/heads/fix/win-program-resolve\n5229bbc7455f144bddfa7acfe877a36221fdaf21 refs/heads/m10-digest-extractor\n672b9288e5669c023bb3774abf8a339165e70082 refs/heads/m11-shell-substrate\n2da139504d9abeaae4ad0430b47a0dc1f64648ef refs/heads/m12-w1-bringup-rc\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/main\n08c476fe81d3c4c26812917d9acc28ef6000611c refs/heads/pump-carrier-fix\n74e1a9b6b1db086adafd6ae340aa4ba53e3b0510 refs/heads/simple-fixes\n53084318828c9c159adbcb2191bab90dc3310095 refs/heads/update-fetch-ux\n71e4894516600692413c685770d9bd2dff2fab60 refs/heads/v0.10.0-picker-status\nd8f0cbac586e9d6443a9050ccad2183ff817502f refs/heads/v0.11.0-findings\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/v0.12.0-lifecycle\nec162c71a916e33eaabc6e176a1ecbf20b368227 refs/heads/v0.12.1-help-md\n86f20ac831033a20bd5e096b1c318a75249179f3 refs/heads/v0.12.1-lifecycle\n45cc479d903ae1ab8739fade63d8047db0789caa refs/heads/v0.13.0-delivery-control\n71378d8956ea8592c7dd16229f397810149f3fc1 refs/heads/v0.13.0-p1b-ack-deadlock\n7c377f0d18d39b2912d7722aec382221983b41b0 refs/heads/v0.13.2-adapter-packaging\n582e5d10b1964752378bf0ccbfdea76ec938b649 refs/heads/v0.15.0-messaging\n5501ab4d553fbf59c15819bf67ca9fc650de5daa refs/heads/v0.16.0-update-arc\n04dc7e9be386b7b93535d575a088634f0ab7481c refs/heads/v0.17.0-robust-join\n28d6e70760b27a66bb63231e7ea3c480f77d534c refs/heads/v0.8.1-livehost-bootrace\n3547a175eba02ce0b225cbdcf5f3a512c539b76b refs/heads/v0.8.2-templating-residency\nf4dc053faf568ab32dfdd2dcf6141b885d763734 refs/heads/v0.8.3-broker-quic-deadline\n0d7fb6d30bb4880d9c268b3d4519eaeab863bd8f refs/heads/v0.9.0-harness-resolution\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/v0.9.1-picker-status\n07158ca0e98a56ec1b6641a4869b56110aa6446f refs/heads/v0.9.1-resolve-dotstem\n40bd6516ee833f53631fbe826296a2952fe2cf55 refs/heads/viewer-drain-decouple-b4\nce48da961a0eb4393cadace7b3b0478cab311bfb refs/heads/wedge-trace-instrumentation\na87ecda1cc8f95ffe59f661cf861ce6de3a33c29 refs/heads/wedge-trace-v2\nfc3af2a37646b267e65e06b0f3bd2be33f24fcdb refs/heads/wedge-trace-v3\n805727c5cda493066d3a8c1dc0678d55f2d0cff6 refs/heads/wedge-trace-v4\n0760ec50684464c09d5931848b30031ef9ffd9dd refs/remotes/origin/design/endpoint-creation-flow\nc5887db27f6c8576decec071c073e76c5144ba66 refs/remotes/origin/dev-freeform\n17e13d94863b6c3d8710d75ad0970205159968a7 refs/remotes/origin/docs/release-bump-in-pr\n220692cce779dc5c3f29f9011338c145adf80c90 refs/remotes/origin/docs/v0140-coverage\naa5fa2a42e7ab13045d1827eefcddade797bac73 refs/remotes/origin/feat/translate-proof\nec360f161b56fff306e3047b096ee7a1f16bd92c refs/remotes/origin/field-bug-milestone\nb4275c3c4fcce720bf244b32f42a34b18d419443 refs/remotes/origin/fix/digest-proof-session-id-keys\nbe3d207723b2e59b01bbbc58151221af0504c58f refs/remotes/origin/fix/win-program-resolve\n5229bbc7455f144bddfa7acfe877a36221fdaf21 refs/remotes/origin/m10-digest-extractor\n672b9288e5669c023bb3774abf8a339165e70082 refs/remotes/origin/m11-shell-substrate\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/remotes/origin/main\n7c098192f2b83810fdd1dc76a2b6ec354bac94f7 refs/remotes/origin/pump-carrier-fix\n74e1a9b6b1db086adafd6ae340aa4ba53e3b0510 refs/remotes/origin/simple-fixes\n53084318828c9c159adbcb2191bab90dc3310095 refs/remotes/origin/update-fetch-ux\n9e5cb8b415763fc67f114890b2ca8fdbedb15386 refs/remotes/origin/v0.10.0-picker-status\nd8f0cbac586e9d6443a9050ccad2183ff817502f refs/remotes/origin/v0.11.0-findings\n6de905b547de7fc6ab764657a5fa8b26eaad16d0 refs/remotes/origin/v0.12.0-lifecycle\n0d8ed2e2cdf8b63590eb1d63cf2dea1aae29fdbb refs/remotes/origin/v0.12.1-lifecycle\n45cc479d903ae1ab8739fade63d8047db0789caa refs/remotes/origin/v0.13.0-delivery-control\n71378d8956ea8592c7dd16229f397810149f3fc1 refs/remotes/origin/v0.13.0-p1b-ack-deadlock\n7c377f0d18d39b2912d7722aec382221983b41b0 refs/remotes/origin/v0.13.2-adapter-packaging\n5501ab4d553fbf59c15819bf67ca9fc650de5daa refs/remotes/origin/v0.16.0-update-arc\n04dc7e9be386b7b93535d575a088634f0ab7481c refs/remotes/origin/v0.17.0-robust-join\n28d6e70760b27a66bb63231e7ea3c480f77d534c refs/remotes/origin/v0.8.1-livehost-bootrace\n3547a175eba02ce0b225cbdcf5f3a512c539b76b refs/remotes/origin/v0.8.2-templating-residency\nf4dc053faf568ab32dfdd2dcf6141b885d763734 refs/remotes/origin/v0.8.3-broker-quic-deadline\n0d7fb6d30bb4880d9c268b3d4519eaeab863bd8f refs/remotes/origin/v0.9.0-harness-resolution\n07158ca0e98a56ec1b6641a4869b56110aa6446f refs/remotes/origin/v0.9.1-resolve-dotstem\n40bd6516ee833f53631fbe826296a2952fe2cf55 refs/remotes/origin/viewer-drain-decouple-b4\nce48da961a0eb4393cadace7b3b0478cab311bfb refs/remotes/origin/wedge-trace-instrumentation\na87ecda1cc8f95ffe59f661cf861ce6de3a33c29 refs/remotes/origin/wedge-trace-v2\nfc3af2a37646b267e65e06b0f3bd2be33f24fcdb refs/remotes/origin/wedge-trace-v3\n805727c5cda493066d3a8c1dc0678d55f2d0cff6 refs/remotes/origin/wedge-trace-v4\n796faa717b02f9fa58e6b59bbf3418c7ad1b5c27 refs/tags/v0.1.0\nf75de3f1690dbb8bb3f1116bc830fb5539a792ad refs/tags/v0.1.1\n^a75f77f5b1048b50f2ad606885a7cf68fe207d86\n78c273240c1c727652dd11e50c46dcbcbbb12335 refs/tags/v0.10.0\n^71e4894516600692413c685770d9bd2dff2fab60\n265392c1d667f58a9fef886c02f6312da1c09f10 refs/tags/v0.11.0\n^27e06195a599107f565f4507aa5aa96ae9fe1ef5\na807f14aee32f51d475fc069705ffd93779d5606 refs/tags/v0.12.0\n^c17550dad70f8818120ff2017619f4e7f658818a\n86f20ac831033a20bd5e096b1c318a75249179f3 refs/tags/v0.12.1\nd263a940b12fa8d0411c25f45d816acfa117e0dc refs/tags/v0.13.0\n4d5a0cbe82475d08bf2d4c8029b187bfd4cc819a refs/tags/v0.13.1\n2bb9c8f7fd85551e5f19c191292c80f778e54346 refs/tags/v0.13.2\n6ba875eb189bff289f336735cbf8138996cf6d7f refs/tags/v0.14.0\n46289ab54f7b334e8a390e217b69232ec76cf405 refs/tags/v0.14.1\n365fb0cbe8422795d03bbc2fe1efdd267a613b81 refs/tags/v0.14.2\n90852bf1bfb8832bf33a27ee58541be2618ec592 refs/tags/v0.14.3\n18d50459c1849d5ed58d427447871f870ff47edd refs/tags/v0.15.0\n9e5b33bb3ed1f6ed6766f5f17373e43ce2778932 refs/tags/v0.16.0\na8baa237c666a529459adc5996990686b388faa6 refs/tags/v0.17.0\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/tags/v0.18.0\n78c4a7871e15f84c6784ca91323036b052485629 refs/tags/v0.2.0\n24cd3829374103f474cbcab879b1f32d99344f8c refs/tags/v0.3.0\n^7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f\ne56d4daf6e537324b1065d58a19546d403d88d2d refs/tags/v0.3.1\n^c5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f\n4057733bb560645c73b14d6a4c7a14b24ff8f42c refs/tags/v0.3.2\n^cabdc614fc0416e3a57de0a21b229db485089320\nb0fdc015fd37172ab3782fcc71878cfa67da7b3d refs/tags/v0.4.0\n038257bb9c4a405244a6724beaf2ececb30e7111 refs/tags/v0.4.1\n725aebf647f8ebe4415fcfd2274488b41cc561f0 refs/tags/v0.4.2\n22a9b6fcbf2b11afc527d3847ee59c2045baee52 refs/tags/v0.5.0\n6a9ff4e9e12f010bbca954153330460260fd5151 refs/tags/v0.6.0\ne08cea0dd604d378c77233876ff9f9d895d6ff7e refs/tags/v0.7.0\ncb0560052c7392d386d9e68db359bbbf3138dcfe refs/tags/v0.7.1\n1582e32ba704e7cad770a42265b5c90e044ace37 refs/tags/v0.7.2\n22a9c58cfb7b5259dff99670012108095cb3e376 refs/tags/v0.7.3\n70294ef2c20f3b07866eb97f9e9ac60ee61460e9 refs/tags/v0.8.0\n325431aeaa321d26bca4cc83e8769bb84b77d9c7 refs/tags/v0.8.1\n4ac17c29978c0a087682a343cef4befc1aff9c3d refs/tags/v0.8.2\n8ed7fcf4ec1db98a36b3baf9e34a162a2a9a1d96 refs/tags/v0.8.3\n^b6470c1c4ababb407d44ed2c38b4354485f0b6b1\n29a71705483f0fc3f5a9c726b7972d1142276e0e refs/tags/v0.8.4\n^c9ff19f1fd313b312395cdf4eace60cbe677e007\na1243ee1585e94674ee38b0858ef1aafb2465875 refs/tags/v0.9.0\n^c448b321bef3ea452184f974f2c6b547de058653\nab1d738361d9c607e0a918f9839fda96da723839 refs/tags/v0.9.1\n^7f93ecd59a900f4890f787dd56e2c69d8ededaec\n"}],"details":{"displayContent":{"text":"# pack-refs with: peeled fully-peeled sorted \n1941eb823b36cc6ecc95dcbffe49b5a2047335fd refs/heads/design/endpoint-creation-flow\nc5887db27f6c8576decec071c073e76c5144ba66 refs/heads/dev-freeform\nf747e1e7048831b51c8aed18a611d10238c3eda4 refs/heads/docs/msg-envelope-adr-0020\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/doyle-work\naa5fa2a42e7ab13045d1827eefcddade797bac73 refs/heads/feat/translate-proof\n3da4aa269253b7b8bf0b3c9dca31c6e5ee2f51d1 refs/heads/field-bug-milestone\nb4275c3c4fcce720bf244b32f42a34b18d419443 refs/heads/fix/digest-proof-session-id-keys\n4fd028d5ceda811557f361162f50987db929eb02 refs/heads/fix/f019-translation-install-dir-resolve\n60fadd25f3b62712607822692d4b7596b4a68de3 refs/heads/fix/v0143-raw-inject-removal\nbe3d207723b2e59b01bbbc58151221af0504c58f refs/heads/fix/win-program-resolve\n5229bbc7455f144bddfa7acfe877a36221fdaf21 refs/heads/m10-digest-extractor\n672b9288e5669c023bb3774abf8a339165e70082 refs/heads/m11-shell-substrate\n2da139504d9abeaae4ad0430b47a0dc1f64648ef refs/heads/m12-w1-bringup-rc\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/main\n08c476fe81d3c4c26812917d9acc28ef6000611c refs/heads/pump-carrier-fix\n74e1a9b6b1db086adafd6ae340aa4ba53e3b0510 refs/heads/simple-fixes\n53084318828c9c159adbcb2191bab90dc3310095 refs/heads/update-fetch-ux\n71e4894516600692413c685770d9bd2dff2fab60 refs/heads/v0.10.0-picker-status\nd8f0cbac586e9d6443a9050ccad2183ff817502f refs/heads/v0.11.0-findings\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/v0.12.0-lifecycle\nec162c71a916e33eaabc6e176a1ecbf20b368227 refs/heads/v0.12.1-help-md\n86f20ac831033a20bd5e096b1c318a75249179f3 refs/heads/v0.12.1-lifecycle\n45cc479d903ae1ab8739fade63d8047db0789caa refs/heads/v0.13.0-delivery-control\n71378d8956ea8592c7dd16229f397810149f3fc1 refs/heads/v0.13.0-p1b-ack-deadlock\n7c377f0d18d39b2912d7722aec382221983b41b0 refs/heads/v0.13.2-adapter-packaging\n582e5d10b1964752378bf0ccbfdea76ec938b649 refs/heads/v0.15.0-messaging\n5501ab4d553fbf59c15819bf67ca9fc650de5daa refs/heads/v0.16.0-update-arc\n04dc7e9be386b7b93535d575a088634f0ab7481c refs/heads/v0.17.0-robust-join\n28d6e70760b27a66bb63231e7ea3c480f77d534c refs/heads/v0.8.1-livehost-bootrace\n3547a175eba02ce0b225cbdcf5f3a512c539b76b refs/heads/v0.8.2-templating-residency\nf4dc053faf568ab32dfdd2dcf6141b885d763734 refs/heads/v0.8.3-broker-quic-deadline\n0d7fb6d30bb4880d9c268b3d4519eaeab863bd8f refs/heads/v0.9.0-harness-resolution\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/v0.9.1-picker-status\n07158ca0e98a56ec1b6641a4869b56110aa6446f refs/heads/v0.9.1-resolve-dotstem\n40bd6516ee833f53631fbe826296a2952fe2cf55 refs/heads/viewer-drain-decouple-b4\nce48da961a0eb4393cadace7b3b0478cab311bfb refs/heads/wedge-trace-instrumentation\na87ecda1cc8f95ffe59f661cf861ce6de3a33c29 refs/heads/wedge-trace-v2\nfc3af2a37646b267e65e06b0f3bd2be33f24fcdb refs/heads/wedge-trace-v3\n805727c5cda493066d3a8c1dc0678d55f2d0cff6 refs/heads/wedge-trace-v4\n0760ec50684464c09d5931848b30031ef9ffd9dd refs/remotes/origin/design/endpoint-creation-flow\nc5887db27f6c8576decec071c073e76c5144ba66 refs/remotes/origin/dev-freeform\n17e13d94863b6c3d8710d75ad0970205159968a7 refs/remotes/origin/docs/release-bump-in-pr\n220692cce779dc5c3f29f9011338c145adf80c90 refs/remotes/origin/docs/v0140-coverage\naa5fa2a42e7ab13045d1827eefcddade797bac73 refs/remotes/origin/feat/translate-proof\nec360f161b56fff306e3047b096ee7a1f16bd92c refs/remotes/origin/field-bug-milestone\nb4275c3c4fcce720bf244b32f42a34b18d419443 refs/remotes/origin/fix/digest-proof-session-id-keys\nbe3d207723b2e59b01bbbc58151221af0504c58f refs/remotes/origin/fix/win-program-resolve\n5229bbc7455f144bddfa7acfe877a36221fdaf21 refs/remotes/origin/m10-digest-extractor\n672b9288e5669c023bb3774abf8a339165e70082 refs/remotes/origin/m11-shell-substrate\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/remotes/origin/main\n7c098192f2b83810fdd1dc76a2b6ec354bac94f7 refs/remotes/origin/pump-carrier-fix\n74e1a9b6b1db086adafd6ae340aa4ba53e3b0510 refs/remotes/origin/simple-fixes\n53084318828c9c159adbcb2191bab90dc3310095 refs/remotes/origin/update-fetch-ux\n9e5cb8b415763fc67f114890b2ca8fdbedb15386 refs/remotes/origin/v0.10.0-picker-status\nd8f0cbac586e9d6443a9050ccad2183ff817502f refs/remotes/origin/v0.11.0-findings\n6de905b547de7fc6ab764657a5fa8b26eaad16d0 refs/remotes/origin/v0.12.0-lifecycle\n0d8ed2e2cdf8b63590eb1d63cf2dea1aae29fdbb refs/remotes/origin/v0.12.1-lifecycle\n45cc479d903ae1ab8739fade63d8047db0789caa refs/remotes/origin/v0.13.0-delivery-control\n71378d8956ea8592c7dd16229f397810149f3fc1 refs/remotes/origin/v0.13.0-p1b-ack-deadlock\n7c377f0d18d39b2912d7722aec382221983b41b0 refs/remotes/origin/v0.13.2-adapter-packaging\n5501ab4d553fbf59c15819bf67ca9fc650de5daa refs/remotes/origin/v0.16.0-update-arc\n04dc7e9be386b7b93535d575a088634f0ab7481c refs/remotes/origin/v0.17.0-robust-join\n28d6e70760b27a66bb63231e7ea3c480f77d534c refs/remotes/origin/v0.8.1-livehost-bootrace\n3547a175eba02ce0b225cbdcf5f3a512c539b76b refs/remotes/origin/v0.8.2-templating-residency\nf4dc053faf568ab32dfdd2dcf6141b885d763734 refs/remotes/origin/v0.8.3-broker-quic-deadline\n0d7fb6d30bb4880d9c268b3d4519eaeab863bd8f refs/remotes/origin/v0.9.0-harness-resolution\n07158ca0e98a56ec1b6641a4869b56110aa6446f refs/remotes/origin/v0.9.1-resolve-dotstem\n40bd6516ee833f53631fbe826296a2952fe2cf55 refs/remotes/origin/viewer-drain-decouple-b4\nce48da961a0eb4393cadace7b3b0478cab311bfb refs/remotes/origin/wedge-trace-instrumentation\na87ecda1cc8f95ffe59f661cf861ce6de3a33c29 refs/remotes/origin/wedge-trace-v2\nfc3af2a37646b267e65e06b0f3bd2be33f24fcdb refs/remotes/origin/wedge-trace-v3\n805727c5cda493066d3a8c1dc0678d55f2d0cff6 refs/remotes/origin/wedge-trace-v4\n796faa717b02f9fa58e6b59bbf3418c7ad1b5c27 refs/tags/v0.1.0\nf75de3f1690dbb8bb3f1116bc830fb5539a792ad refs/tags/v0.1.1\n^a75f77f5b1048b50f2ad606885a7cf68fe207d86\n78c273240c1c727652dd11e50c46dcbcbbb12335 refs/tags/v0.10.0\n^71e4894516600692413c685770d9bd2dff2fab60\n265392c1d667f58a9fef886c02f6312da1c09f10 refs/tags/v0.11.0\n^27e06195a599107f565f4507aa5aa96ae9fe1ef5\na807f14aee32f51d475fc069705ffd93779d5606 refs/tags/v0.12.0\n^c17550dad70f8818120ff2017619f4e7f658818a\n86f20ac831033a20bd5e096b1c318a75249179f3 refs/tags/v0.12.1\nd263a940b12fa8d0411c25f45d816acfa117e0dc refs/tags/v0.13.0\n4d5a0cbe82475d08bf2d4c8029b187bfd4cc819a refs/tags/v0.13.1\n2bb9c8f7fd85551e5f19c191292c80f778e54346 refs/tags/v0.13.2\n6ba875eb189bff289f336735cbf8138996cf6d7f refs/tags/v0.14.0\n46289ab54f7b334e8a390e217b69232ec76cf405 refs/tags/v0.14.1\n365fb0cbe8422795d03bbc2fe1efdd267a613b81 refs/tags/v0.14.2\n90852bf1bfb8832bf33a27ee58541be2618ec592 refs/tags/v0.14.3\n18d50459c1849d5ed58d427447871f870ff47edd refs/tags/v0.15.0\n9e5b33bb3ed1f6ed6766f5f17373e43ce2778932 refs/tags/v0.16.0\na8baa237c666a529459adc5996990686b388faa6 refs/tags/v0.17.0\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/tags/v0.18.0\n78c4a7871e15f84c6784ca91323036b052485629 refs/tags/v0.2.0\n24cd3829374103f474cbcab879b1f32d99344f8c refs/tags/v0.3.0\n^7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f\ne56d4daf6e537324b1065d58a19546d403d88d2d refs/tags/v0.3.1\n^c5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f\n4057733bb560645c73b14d6a4c7a14b24ff8f42c refs/tags/v0.3.2\n^cabdc614fc0416e3a57de0a21b229db485089320\nb0fdc015fd37172ab3782fcc71878cfa67da7b3d refs/tags/v0.4.0\n038257bb9c4a405244a6724beaf2ececb30e7111 refs/tags/v0.4.1\n725aebf647f8ebe4415fcfd2274488b41cc561f0 refs/tags/v0.4.2\n22a9b6fcbf2b11afc527d3847ee59c2045baee52 refs/tags/v0.5.0\n6a9ff4e9e12f010bbca954153330460260fd5151 refs/tags/v0.6.0\ne08cea0dd604d378c77233876ff9f9d895d6ff7e refs/tags/v0.7.0\ncb0560052c7392d386d9e68db359bbbf3138dcfe refs/tags/v0.7.1\n1582e32ba704e7cad770a42265b5c90e044ace37 refs/tags/v0.7.2\n22a9c58cfb7b5259dff99670012108095cb3e376 refs/tags/v0.7.3\n70294ef2c20f3b07866eb97f9e9ac60ee61460e9 refs/tags/v0.8.0\n325431aeaa321d26bca4cc83e8769bb84b77d9c7 refs/tags/v0.8.1\n4ac17c29978c0a087682a343cef4befc1aff9c3d refs/tags/v0.8.2\n8ed7fcf4ec1db98a36b3baf9e34a162a2a9a1d96 refs/tags/v0.8.3\n^b6470c1c4ababb407d44ed2c38b4354485f0b6b1\n29a71705483f0fc3f5a9c726b7972d1142276e0e refs/tags/v0.8.4\n^c9ff19f1fd313b312395cdf4eace60cbe677e007\na1243ee1585e94674ee38b0858ef1aafb2465875 refs/tags/v0.9.0\n^c448b321bef3ea452184f974f2c6b547de058653\nab1d738361d9c607e0a918f9839fda96da723839 refs/tags/v0.9.1\n^7f93ecd59a900f4890f787dd56e2c69d8ededaec\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\packed-refs"}}},"isError":false,"timestamp":1784247370239}}
{"type":"message","id":"a9d523af","parentId":"34a96a28","timestamp":"2026-07-17T00:16:10.244Z","message":{"role":"toolResult","toolCallId":"call_VmOpF2ahnneopSywQYepVfIh|fc_099a7567dadef391016a59744a3100819b9fcae2429cbb2fd5","toolName":"read","content":[{"type":"text","text":"# spt-core cargo workspace (M0). Acyclically-layered crates per R-ARCH-1:\n#   spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime} -> spt-live -> spt-daemon -> spt\n# M0 builds the bottom two: spt-proto (wire) + spt-store (persistence).\n# M1 adds spt-msg (local messaging) — third layer, one-way above spt-store.\n# M2a adds spt-runtime (harness contract: manifest schema + AgentRuntime) — fourth layer.\n# M2b adds spt-live (live-agent lifecycle executor) — fifth layer, daemon-bound (not public SDK).\n# M3a adds spt-term (session-surface mechanism: ConPTY/forkpty PTY layer) — sibling of spt-runtime, above spt-msg.\n# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).\n# M4 adds spt-net (P2P WAN/LAN transport) — sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.\n[workspace]\nresolver = \"2\"\nmembers = [\"crates/spt-proto\", \"crates/spt-store\", \"crates/spt-msg\", \"crates/spt-net\", \"crates/spt-term\", \"crates/spt-runtime\", \"crates/spt-live\", \"crates/spt-daemon\", \"crates/spt\", \"adapters/mock\", \"crates/xtask\"]\n\n# M6-D6 (grill decision 9): ONE shared version for every crate and the binary\n# — the product version (v0.1.0 tag, `spt --version`). The wire-protocol\n# version stays its own counter (REQ-ARCH-3) and the SignedRelease `version`\n# is the separate monotonic release counter; neither conflates with this.\n[workspace.package]\nversion = \"0.35.0\"\n"}],"details":{"displayContent":{"text":"# spt-core cargo workspace (M0). Acyclically-layered crates per R-ARCH-1:\r\n#   spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime} -> spt-live -> spt-daemon -> spt\r\n# M0 builds the bottom two: spt-proto (wire) + spt-store (persistence).\r\n# M1 adds spt-msg (local messaging) — third layer, one-way above spt-store.\r\n# M2a adds spt-runtime (harness contract: manifest schema + AgentRuntime) — fourth layer.\r\n# M2b adds spt-live (live-agent lifecycle executor) — fifth layer, daemon-bound (not public SDK).\r\n# M3a adds spt-term (session-surface mechanism: ConPTY/forkpty PTY layer) — sibling of spt-runtime, above spt-msg.\r\n# M3b adds spt-daemon (broker/brain supervisor) — seventh layer, above spt-live, below spt; internal (not public SDK).\r\n# M4 adds spt-net (P2P WAN/LAN transport) — sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.\r\n[workspace]\r\nresolver = \"2\"\r\nmembers = [\"crates/spt-proto\", \"crates/spt-store\", \"crates/spt-msg\", \"crates/spt-net\", \"crates/spt-term\", \"crates/spt-runtime\", \"crates/spt-live\", \"crates/spt-daemon\", \"crates/spt\", \"adapters/mock\", \"crates/xtask\"]\r\n\r\n# M6-D6 (grill decision 9): ONE shared version for every crate and the binary\r\n# — the product version (v0.1.0 tag, `spt --version`). The wire-protocol\r\n# version stays its own counter (REQ-ARCH-3) and the SignedRelease `version`\r\n# is the separate monotonic release counter; neither conflates with this.\r\n[workspace.package]\r\nversion = \"0.35.0\"\r\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\Cargo.toml"}}},"isError":false,"timestamp":1784247370244}}
{"type":"message","id":"ffba78f3","parentId":"a9d523af","timestamp":"2026-07-17T00:16:10.248Z","message":{"role":"toolResult","toolCallId":"call_5bLc29hcmrAkvOXsliySdenI|fc_099a7567dadef391016a59744a3104819b881d42ed2e89666b","toolName":"read","content":[{"type":"text","text":"# M6-D6: the tag-triggered release pipeline (grill decision 9, ADR-0015).\n#\n# CI's half of the ceremony: build the platform binaries on both self-hosted\n# runners, assemble the asset set the self-updater consumes (binaries,\n# SHA256SUMS, manifest.schema.json, mock-adapter.zip, and the docs bundle\n# spt-docs.tar.gz — ADR-0036 §4), and create a DRAFT release on the private\n# channel BigscreenVR/spt-bs-releases (cross-repo via the RELEASES_TOKEN\n# fine-grained PAT).\n#\n# The maintainer's half stays manual and local (no signing keys in CI) —\n# normally one command that downloads, verifies, signs (binaries + the docs\n# entry in the update set), uploads, and flips the draft:\n#   SPT_RELEASE_SEED=<seed> cargo run -p xtask -- release-publish \\\n#     --tag <vX.Y.Z> --key-id rel-primary-2026 --version <N>\n#\n# [impl->REQ-REL-2]\n# [impl->REQ-RELEASE-CHANNEL-PRIVATE]\nname: release\n\non:\n  push:\n    tags: [\"v*\"]\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        # Asset names MUST match the SUPPORTED_PLATFORMS registry rows\n        # (crates/spt-daemon/src/release.rs) — the update-set + installer key on\n        # them. `target` empty = the host-default toolchain (gnu / msvc); a set\n        # `target` cross-builds that Rust triple (the static musl tier, W3).\n        # [impl->REQ-RELEASE-MUSL-ARTIFACT]\n        include:\n          - runner: [self-hosted, Linux, kitsubito]\n            asset: spt-x86_64-linux\n            built: target/release/spt\n            target: \"\"\n          - runner: [self-hosted, Windows, hfenduleam]\n            asset: spt-x86_64-windows.exe\n            built: target/release/spt.exe\n            target: \"\"\n          # The static-glibc-free Linux artifact — built on kitsubito (the proven\n          # musl toolchain box) so it runs on sub-2.39-glibc hosts (CONTEXT.md:828).\n          - runner: [self-hosted, Linux, kitsubito]\n            asset: spt-x86_64-linux-musl\n            built: target/x86_64-unknown-linux-musl/release/spt\n            target: x86_64-unknown-linux-musl\n    runs-on: ${{ matrix.runner }}\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v5\n      # musl cross-build prerequisites — installed in-job on kitsubito. musl-gcc\n      # (musl-tools) + cmake compile aws-lc-sys' C + bundled sqlite statically;\n      # the Rust target adds the std for the triple. Idempotent (a no-op if the\n      # box already carries them).\n      - name: Install musl toolchain\n        if: matrix.target != ''\n        run: |\n          sudo apt-get update\n          sudo apt-get install -y musl-tools cmake\n          rustup target add \"${{ matrix.target }}\"\n      - name: Build release binary\n        if: matrix.target == ''\n        run: cargo build --release -p spt --bin spt\n      # Static musl build: CC_x86_64_unknown_linux_musl routes the C deps through\n      # musl-gcc so the artifact has no dynamic glibc dependency.\n      - name: Build release binary (musl)\n        if: matrix.target != ''\n        env:\n          CC_x86_64_unknown_linux_musl: musl-gcc\n        run: cargo build --release -p spt --bin spt --target \"${{ matrix.target }}\"\n      # Two staging steps: the Windows runner has no bash on PATH (the same\n      # fact the dormancy-budget steps already encode) — each OS keeps its\n      # native shell.\n      - name: Stage asset (linux)\n        if: runner.os == 'Linux'\n        run: |\n          mkdir -p dist\n          cp \"${{ matrix.built }}\" \"dist/${{ matrix.asset }}\"\n      - name: Stage asset (windows)\n        if: runner.os == 'Windows'\n        # pwsh (PowerShell 7), not Windows PowerShell 5.1: the runner's 5.1\n        # execution policy blocks GitHub's dot-sourced temp script\n        # (PSSecurityException). pwsh is the runner's default shell (the build\n        # step above runs under it) and carries its own, permissive policy.\n        shell: pwsh\n        run: |\n          New-Item -ItemType Directory -Force dist | Out-Null\n          Copy-Item \"${{ matrix.built }}\" \"dist/${{ matrix.asset }}\"\n      - uses: actions/upload-artifact@v4\n        with:\n          name: ${{ matrix.asset }}\n          path: dist/${{ matrix.asset }}\n          if-no-files-found: error\n\n  assemble:\n    needs: build\n    runs-on: [self-hosted, Linux, kitsubito]\n    timeout-minutes: 15\n    steps:\n      - uses: actions/checkout@v5\n      - uses: actions/download-artifact@v4\n        with:\n          path: dist\n          merge-multiple: true\n      - name: Assemble asset set\n        # The exact set REQ-REL-2 names: platform binaries + SHA256SUMS (what\n        # the bootstrap flow verifies) + the manifest schema + the mock-adapter\n        # source zip (the adapter quickstart's reference download).\n        # The `spt-x86_64-*` glob picks up every platform binary the build matrix\n        # produced (gnu, windows, musl, and any future registry row) — no\n        # per-platform literal to keep in sync with SUPPORTED_PLATFORMS.\n        # [impl->REQ-RELEASE-MUSL-ARTIFACT]\n        run: |\n          cd dist\n          sha256sum spt-x86_64-* > SHA256SUMS\n          cp ../crates/spt-runtime/manifest.schema.json .\n          # Zip rooted at the adapter dir so `unzip -d mock-adapter` yields\n          # mock-adapter/manifest.toml exactly as the adapter quickstart says.\n          (cd ../adapters/mock && zip -r \"$OLDPWD/mock-adapter.zip\" . \\\n             -x 'target/*')\n          ls -la\n      - name: Ensure mdbook\n        # Self-healing (the retired docs-publish.yml's step, moved here with\n        # the bundle): kitsubito keeps a cargo toolchain; install once.\n        run: |\n          if ! command -v mdbook >/dev/null && [ ! -x \"$HOME/.cargo/bin/mdbook\" ]; then\n            cargo install mdbook --locked\n          fi\n          export PATH=\"$HOME/.cargo/bin:$PATH\"\n          mdbook --version\n      - name: Build docs bundle\n        # The platform-independent docs asset (ADR-0036 §4): built book +\n        # llms exports + raw .md + schema, one tar.gz. Signed into the\n        # update set by the maintainer's release-publish half.\n        # [impl->REQ-DOCS-RELEASE-ASSET]\n        run: |\n          export PATH=\"$HOME/.cargo/bin:$PATH\"\n          cargo run -p xtask -- docs-bundle dist/spt-docs.tar.gz\n      - name: Build user-facing release notes from CHANGELOG\n        # The release BODY is the user-facing changelog: the `## [<version>]`\n        # section of CHANGELOG.md (Added / Changed / Fixed, UX items only — no\n        # internal lingo). Authoring that section before tagging is a required\n        # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the\n        # release loudly rather than shipping an empty/boilerplate body — the\n        # changelog is not optional.\n        run: |\n          ver=\"${GITHUB_REF_NAME#v}\"   # v0.1.2 -> 0.1.2\n          notes=\"dist/RELEASE_NOTES.md\"\n          # Literal string matching (index, not regex) so the section header is\n          # matched identically across awk dialects — no bracket-escaping traps.\n          awk -v want=\"## [${ver}]\" '\n            index($0, want) == 1 { grab=1; next }\n            grab && index($0, \"## [\") == 1 { exit }\n            grab { print }\n          ' CHANGELOG.md > \"$notes\"\n          if ! grep -q '[^[:space:]]' \"$notes\"; then\n            echo \"::error::CHANGELOG.md has no '## [$ver]' section — add the user-facing changelog for this release before tagging (see docs/RELEASE-RUNBOOK.md).\"\n            exit 1\n          fi\n          echo \"----- release body for ${GITHUB_REF_NAME} -----\"\n          cat \"$notes\"\n      - name: Create draft release on the private channel\n        # [impl->REQ-RELEASE-CHANNEL-PRIVATE] publish target = bs-releases\n        # (ADR-0036 §2); counter/signing/update-set format continue unchanged.\n        # [int->REQ-RELEASE-CHANNEL-PRIVATE] the v0.32.0 (counter 59) ceremony\n        # exercises this assemble/draft leg end-to-end against the private\n        # channel — the first release on BigscreenVR/spt-bs-releases (W2 ruling:\n        # the release ceremony is the integration evidence, no test rig).\n        env:\n          GH_TOKEN: ${{ secrets.RELEASES_TOKEN }}\n        run: |\n          gh release create \"${GITHUB_REF_NAME}\" \\\n            --repo BigscreenVR/spt-bs-releases \\\n            --draft \\\n            --title \"spt ${GITHUB_REF_NAME}\" \\\n            --notes-file dist/RELEASE_NOTES.md \\\n            dist/spt-x86_64-* \\\n            dist/SHA256SUMS \\\n            dist/manifest.schema.json \\\n            dist/mock-adapter.zip \\\n            dist/spt-docs.tar.gz\n\n[Showing lines 1-183 of 184. Use :184 to continue]"}],"details":{"truncation":{"content":"# M6-D6: the tag-triggered release pipeline (grill decision 9, ADR-0015).\r\n#\r\n# CI's half of the ceremony: build the platform binaries on both self-hosted\r\n# runners, assemble the asset set the self-updater consumes (binaries,\r\n# SHA256SUMS, manifest.schema.json, mock-adapter.zip, and the docs bundle\r\n# spt-docs.tar.gz — ADR-0036 §4), and create a DRAFT release on the private\r\n# channel BigscreenVR/spt-bs-releases (cross-repo via the RELEASES_TOKEN\r\n# fine-grained PAT).\r\n#\r\n# The maintainer's half stays manual and local (no signing keys in CI) —\r\n# normally one command that downloads, verifies, signs (binaries + the docs\r\n# entry in the update set), uploads, and flips the draft:\r\n#   SPT_RELEASE_SEED=<seed> cargo run -p xtask -- release-publish \\\r\n#     --tag <vX.Y.Z> --key-id rel-primary-2026 --version <N>\r\n#\r\n# [impl->REQ-REL-2]\r\n# [impl->REQ-RELEASE-CHANNEL-PRIVATE]\r\nname: release\r\n\r\non:\r\n  push:\r\n    tags: [\"v*\"]\r\n\r\njobs:\r\n  build:\r\n    strategy:\r\n      fail-fast: false\r\n      matrix:\r\n        # Asset names MUST match the SUPPORTED_PLATFORMS registry rows\r\n        # (crates/spt-daemon/src/release.rs) — the update-set + installer key on\r\n        # them. `target` empty = the host-default toolchain (gnu / msvc); a set\r\n        # `target` cross-builds that Rust triple (the static musl tier, W3).\r\n        # [impl->REQ-RELEASE-MUSL-ARTIFACT]\r\n        include:\r\n          - runner: [self-hosted, Linux, kitsubito]\r\n            asset: spt-x86_64-linux\r\n            built: target/release/spt\r\n            target: \"\"\r\n          - runner: [self-hosted, Windows, hfenduleam]\r\n            asset: spt-x86_64-windows.exe\r\n            built: target/release/spt.exe\r\n            target: \"\"\r\n          # The static-glibc-free Linux artifact — built on kitsubito (the proven\r\n          # musl toolchain box) so it runs on sub-2.39-glibc hosts (CONTEXT.md:828).\r\n          - runner: [self-hosted, Linux, kitsubito]\r\n            asset: spt-x86_64-linux-musl\r\n            built: target/x86_64-unknown-linux-musl/release/spt\r\n            target: x86_64-unknown-linux-musl\r\n    runs-on: ${{ matrix.runner }}\r\n    timeout-minutes: 30\r\n    steps:\r\n      - uses: actions/checkout@v5\r\n      # musl cross-build prerequisites — installed in-job on kitsubito. musl-gcc\r\n      # (musl-tools) + cmake compile aws-lc-sys' C + bundled sqlite statically;\r\n      # the Rust target adds the std for the triple. Idempotent (a no-op if the\r\n      # box already carries them).\r\n      - name: Install musl toolchain\r\n        if: matrix.target != ''\r\n        run: |\r\n          sudo apt-get update\r\n          sudo apt-get install -y musl-tools cmake\r\n          rustup target add \"${{ matrix.target }}\"\r\n      - name: Build release binary\r\n        if: matrix.target == ''\r\n        run: cargo build --release -p spt --bin spt\r\n      # Static musl build: CC_x86_64_unknown_linux_musl routes the C deps through\r\n      # musl-gcc so the artifact has no dynamic glibc dependency.\r\n      - name: Build release binary (musl)\r\n        if: matrix.target != ''\r\n        env:\r\n          CC_x86_64_unknown_linux_musl: musl-gcc\r\n        run: cargo build --release -p spt --bin spt --target \"${{ matrix.target }}\"\r\n      # Two staging steps: the Windows runner has no bash on PATH (the same\r\n      # fact the dormancy-budget steps already encode) — each OS keeps its\r\n      # native shell.\r\n      - name: Stage asset (linux)\r\n        if: runner.os == 'Linux'\r\n        run: |\r\n          mkdir -p dist\r\n          cp \"${{ matrix.built }}\" \"dist/${{ matrix.asset }}\"\r\n      - name: Stage asset (windows)\r\n        if: runner.os == 'Windows'\r\n        # pwsh (PowerShell 7), not Windows PowerShell 5.1: the runner's 5.1\r\n        # execution policy blocks GitHub's dot-sourced temp script\r\n        # (PSSecurityException). pwsh is the runner's default shell (the build\r\n        # step above runs under it) and carries its own, permissive policy.\r\n        shell: pwsh\r\n        run: |\r\n          New-Item -ItemType Directory -Force dist | Out-Null\r\n          Copy-Item \"${{ matrix.built }}\" \"dist/${{ matrix.asset }}\"\r\n      - uses: actions/upload-artifact@v4\r\n        with:\r\n          name: ${{ matrix.asset }}\r\n          path: dist/${{ matrix.asset }}\r\n          if-no-files-found: error\r\n\r\n  assemble:\r\n    needs: build\r\n    runs-on: [self-hosted, Linux, kitsubito]\r\n    timeout-minutes: 15\r\n    steps:\r\n      - uses: actions/checkout@v5\r\n      - uses: actions/download-artifact@v4\r\n        with:\r\n          path: dist\r\n          merge-multiple: true\r\n      - name: Assemble asset set\r\n        # The exact set REQ-REL-2 names: platform binaries + SHA256SUMS (what\r\n        # the bootstrap flow verifies) + the manifest schema + the mock-adapter\r\n        # source zip (the adapter quickstart's reference download).\r\n        # The `spt-x86_64-*` glob picks up every platform binary the build matrix\r\n        # produced (gnu, windows, musl, and any future registry row) — no\r\n        # per-platform literal to keep in sync with SUPPORTED_PLATFORMS.\r\n        # [impl->REQ-RELEASE-MUSL-ARTIFACT]\r\n        run: |\r\n          cd dist\r\n          sha256sum spt-x86_64-* > SHA256SUMS\r\n          cp ../crates/spt-runtime/manifest.schema.json .\r\n          # Zip rooted at the adapter dir so `unzip -d mock-adapter` yields\r\n          # mock-adapter/manifest.toml exactly as the adapter quickstart says.\r\n          (cd ../adapters/mock && zip -r \"$OLDPWD/mock-adapter.zip\" . \\\r\n             -x 'target/*')\r\n          ls -la\r\n      - name: Ensure mdbook\r\n        # Self-healing (the retired docs-publish.yml's step, moved here with\r\n        # the bundle): kitsubito keeps a cargo toolchain; install once.\r\n        run: |\r\n          if ! command -v mdbook >/dev/null && [ ! -x \"$HOME/.cargo/bin/mdbook\" ]; then\r\n            cargo install mdbook --locked\r\n          fi\r\n          export PATH=\"$HOME/.cargo/bin:$PATH\"\r\n          mdbook --version\r\n      - name: Build docs bundle\r\n        # The platform-independent docs asset (ADR-0036 §4): built book +\r\n        # llms exports + raw .md + schema, one tar.gz. Signed into the\r\n        # update set by the maintainer's release-publish half.\r\n        # [impl->REQ-DOCS-RELEASE-ASSET]\r\n        run: |\r\n          export PATH=\"$HOME/.cargo/bin:$PATH\"\r\n          cargo run -p xtask -- docs-bundle dist/spt-docs.tar.gz\r\n      - name: Build user-facing release notes from CHANGELOG\r\n        # The release BODY is the user-facing changelog: the `## [<version>]`\r\n        # section of CHANGELOG.md (Added / Changed / Fixed, UX items only — no\r\n        # internal lingo). Authoring that section before tagging is a required\r\n        # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the\r\n        # release loudly rather than shipping an empty/boilerplate body — the\r\n        # changelog is not optional.\r\n        run: |\r\n          ver=\"${GITHUB_REF_NAME#v}\"   # v0.1.2 -> 0.1.2\r\n          notes=\"dist/RELEASE_NOTES.md\"\r\n          # Literal string matching (index, not regex) so the section header is\r\n          # matched identically across awk dialects — no bracket-escaping traps.\r\n          awk -v want=\"## [${ver}]\" '\r\n            index($0, want) == 1 { grab=1; next }\r\n            grab && index($0, \"## [\") == 1 { exit }\r\n            grab { print }\r\n          ' CHANGELOG.md > \"$notes\"\r\n          if ! grep -q '[^[:space:]]' \"$notes\"; then\r\n            echo \"::error::CHANGELOG.md has no '## [$ver]' section — add the user-facing changelog for this release before tagging (see docs/RELEASE-RUNBOOK.md).\"\r\n            exit 1\r\n          fi\r\n          echo \"----- release body for ${GITHUB_REF_NAME} -----\"\r\n          cat \"$notes\"\r\n      - name: Create draft release on the private channel\r\n        # [impl->REQ-RELEASE-CHANNEL-PRIVATE] publish target = bs-releases\r\n        # (ADR-0036 §2); counter/signing/update-set format continue unchanged.\r\n        # [int->REQ-RELEASE-CHANNEL-PRIVATE] the v0.32.0 (counter 59) ceremony\r\n        # exercises this assemble/draft leg end-to-end against the private\r\n        # channel — the first release on BigscreenVR/spt-bs-releases (W2 ruling:\r\n        # the release ceremony is the integration evidence, no test rig).\r\n        env:\r\n          GH_TOKEN: ${{ secrets.RELEASES_TOKEN }}\r\n        run: |\r\n          gh release create \"${GITHUB_REF_NAME}\" \\\r\n            --repo BigscreenVR/spt-bs-releases \\\r\n            --draft \\\r\n            --title \"spt ${GITHUB_REF_NAME}\" \\\r\n            --notes-file dist/RELEASE_NOTES.md \\\r\n            dist/spt-x86_64-* \\\r\n            dist/SHA256SUMS \\\r\n            dist/manifest.schema.json \\\r\n            dist/mock-adapter.zip \\\r\n            dist/spt-docs.tar.gz\r","truncated":true,"truncatedBy":"lines","totalLines":184,"totalBytes":8663,"outputLines":183,"outputBytes":8663,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"# M6-D6: the tag-triggered release pipeline (grill decision 9, ADR-0015).\r\n#\r\n# CI's half of the ceremony: build the platform binaries on both self-hosted\r\n# runners, assemble the asset set the self-updater consumes (binaries,\r\n# SHA256SUMS, manifest.schema.json, mock-adapter.zip, and the docs bundle\r\n# spt-docs.tar.gz — ADR-0036 §4), and create a DRAFT release on the private\r\n# channel BigscreenVR/spt-bs-releases (cross-repo via the RELEASES_TOKEN\r\n# fine-grained PAT).\r\n#\r\n# The maintainer's half stays manual and local (no signing keys in CI) —\r\n# normally one command that downloads, verifies, signs (binaries + the docs\r\n# entry in the update set), uploads, and flips the draft:\r\n#   SPT_RELEASE_SEED=<seed> cargo run -p xtask -- release-publish \\\r\n#     --tag <vX.Y.Z> --key-id rel-primary-2026 --version <N>\r\n#\r\n# [impl->REQ-REL-2]\r\n# [impl->REQ-RELEASE-CHANNEL-PRIVATE]\r\nname: release\r\n\r\non:\r\n  push:\r\n    tags: [\"v*\"]\r\n\r\njobs:\r\n  build:\r\n    strategy:\r\n      fail-fast: false\r\n      matrix:\r\n        # Asset names MUST match the SUPPORTED_PLATFORMS registry rows\r\n        # (crates/spt-daemon/src/release.rs) — the update-set + installer key on\r\n        # them. `target` empty = the host-default toolchain (gnu / msvc); a set\r\n        # `target` cross-builds that Rust triple (the static musl tier, W3).\r\n        # [impl->REQ-RELEASE-MUSL-ARTIFACT]\r\n        include:\r\n          - runner: [self-hosted, Linux, kitsubito]\r\n            asset: spt-x86_64-linux\r\n            built: target/release/spt\r\n            target: \"\"\r\n          - runner: [self-hosted, Windows, hfenduleam]\r\n            asset: spt-x86_64-windows.exe\r\n            built: target/release/spt.exe\r\n            target: \"\"\r\n          # The static-glibc-free Linux artifact — built on kitsubito (the proven\r\n          # musl toolchain box) so it runs on sub-2.39-glibc hosts (CONTEXT.md:828).\r\n          - runner: [self-hosted, Linux, kitsubito]\r\n            asset: spt-x86_64-linux-musl\r\n            built: target/x86_64-unknown-linux-musl/release/spt\r\n            target: x86_64-unknown-linux-musl\r\n    runs-on: ${{ matrix.runner }}\r\n    timeout-minutes: 30\r\n    steps:\r\n      - uses: actions/checkout@v5\r\n      # musl cross-build prerequisites — installed in-job on kitsubito. musl-gcc\r\n      # (musl-tools) + cmake compile aws-lc-sys' C + bundled sqlite statically;\r\n      # the Rust target adds the std for the triple. Idempotent (a no-op if the\r\n      # box already carries them).\r\n      - name: Install musl toolchain\r\n        if: matrix.target != ''\r\n        run: |\r\n          sudo apt-get update\r\n          sudo apt-get install -y musl-tools cmake\r\n          rustup target add \"${{ matrix.target }}\"\r\n      - name: Build release binary\r\n        if: matrix.target == ''\r\n        run: cargo build --release -p spt --bin spt\r\n      # Static musl build: CC_x86_64_unknown_linux_musl routes the C deps through\r\n      # musl-gcc so the artifact has no dynamic glibc dependency.\r\n      - name: Build release binary (musl)\r\n        if: matrix.target != ''\r\n        env:\r\n          CC_x86_64_unknown_linux_musl: musl-gcc\r\n        run: cargo build --release -p spt --bin spt --target \"${{ matrix.target }}\"\r\n      # Two staging steps: the Windows runner has no bash on PATH (the same\r\n      # fact the dormancy-budget steps already encode) — each OS keeps its\r\n      # native shell.\r\n      - name: Stage asset (linux)\r\n        if: runner.os == 'Linux'\r\n        run: |\r\n          mkdir -p dist\r\n          cp \"${{ matrix.built }}\" \"dist/${{ matrix.asset }}\"\r\n      - name: Stage asset (windows)\r\n        if: runner.os == 'Windows'\r\n        # pwsh (PowerShell 7), not Windows PowerShell 5.1: the runner's 5.1\r\n        # execution policy blocks GitHub's dot-sourced temp script\r\n        # (PSSecurityException). pwsh is the runner's default shell (the build\r\n        # step above runs under it) and carries its own, permissive policy.\r\n        shell: pwsh\r\n        run: |\r\n          New-Item -ItemType Directory -Force dist | Out-Null\r\n          Copy-Item \"${{ matrix.built }}\" \"dist/${{ matrix.asset }}\"\r\n      - uses: actions/upload-artifact@v4\r\n        with:\r\n          name: ${{ matrix.asset }}\r\n          path: dist/${{ matrix.asset }}\r\n          if-no-files-found: error\r\n\r\n  assemble:\r\n    needs: build\r\n    runs-on: [self-hosted, Linux, kitsubito]\r\n    timeout-minutes: 15\r\n    steps:\r\n      - uses: actions/checkout@v5\r\n      - uses: actions/download-artifact@v4\r\n        with:\r\n          path: dist\r\n          merge-multiple: true\r\n      - name: Assemble asset set\r\n        # The exact set REQ-REL-2 names: platform binaries + SHA256SUMS (what\r\n        # the bootstrap flow verifies) + the manifest schema + the mock-adapter\r\n        # source zip (the adapter quickstart's reference download).\r\n        # The `spt-x86_64-*` glob picks up every platform binary the build matrix\r\n        # produced (gnu, windows, musl, and any future registry row) — no\r\n        # per-platform literal to keep in sync with SUPPORTED_PLATFORMS.\r\n        # [impl->REQ-RELEASE-MUSL-ARTIFACT]\r\n        run: |\r\n          cd dist\r\n          sha256sum spt-x86_64-* > SHA256SUMS\r\n          cp ../crates/spt-runtime/manifest.schema.json .\r\n          # Zip rooted at the adapter dir so `unzip -d mock-adapter` yields\r\n          # mock-adapter/manifest.toml exactly as the adapter quickstart says.\r\n          (cd ../adapters/mock && zip -r \"$OLDPWD/mock-adapter.zip\" . \\\r\n             -x 'target/*')\r\n          ls -la\r\n      - name: Ensure mdbook\r\n        # Self-healing (the retired docs-publish.yml's step, moved here with\r\n        # the bundle): kitsubito keeps a cargo toolchain; install once.\r\n        run: |\r\n          if ! command -v mdbook >/dev/null && [ ! -x \"$HOME/.cargo/bin/mdbook\" ]; then\r\n            cargo install mdbook --locked\r\n          fi\r\n          export PATH=\"$HOME/.cargo/bin:$PATH\"\r\n          mdbook --version\r\n      - name: Build docs bundle\r\n        # The platform-independent docs asset (ADR-0036 §4): built book +\r\n        # llms exports + raw .md + schema, one tar.gz. Signed into the\r\n        # update set by the maintainer's release-publish half.\r\n        # [impl->REQ-DOCS-RELEASE-ASSET]\r\n        run: |\r\n          export PATH=\"$HOME/.cargo/bin:$PATH\"\r\n          cargo run -p xtask -- docs-bundle dist/spt-docs.tar.gz\r\n      - name: Build user-facing release notes from CHANGELOG\r\n        # The release BODY is the user-facing changelog: the `## [<version>]`\r\n        # section of CHANGELOG.md (Added / Changed / Fixed, UX items only — no\r\n        # internal lingo). Authoring that section before tagging is a required\r\n        # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the\r\n        # release loudly rather than shipping an empty/boilerplate body — the\r\n        # changelog is not optional.\r\n        run: |\r\n          ver=\"${GITHUB_REF_NAME#v}\"   # v0.1.2 -> 0.1.2\r\n          notes=\"dist/RELEASE_NOTES.md\"\r\n          # Literal string matching (index, not regex) so the section header is\r\n          # matched identically across awk dialects — no bracket-escaping traps.\r\n          awk -v want=\"## [${ver}]\" '\r\n            index($0, want) == 1 { grab=1; next }\r\n            grab && index($0, \"## [\") == 1 { exit }\r\n            grab { print }\r\n          ' CHANGELOG.md > \"$notes\"\r\n          if ! grep -q '[^[:space:]]' \"$notes\"; then\r\n            echo \"::error::CHANGELOG.md has no '## [$ver]' section — add the user-facing changelog for this release before tagging (see docs/RELEASE-RUNBOOK.md).\"\r\n            exit 1\r\n          fi\r\n          echo \"----- release body for ${GITHUB_REF_NAME} -----\"\r\n          cat \"$notes\"\r\n      - name: Create draft release on the private channel\r\n        # [impl->REQ-RELEASE-CHANNEL-PRIVATE] publish target = bs-releases\r\n        # (ADR-0036 §2); counter/signing/update-set format continue unchanged.\r\n        # [int->REQ-RELEASE-CHANNEL-PRIVATE] the v0.32.0 (counter 59) ceremony\r\n        # exercises this assemble/draft leg end-to-end against the private\r\n        # channel — the first release on BigscreenVR/spt-bs-releases (W2 ruling:\r\n        # the release ceremony is the integration evidence, no test rig).\r\n        env:\r\n          GH_TOKEN: ${{ secrets.RELEASES_TOKEN }}\r\n        run: |\r\n          gh release create \"${GITHUB_REF_NAME}\" \\\r\n            --repo BigscreenVR/spt-bs-releases \\\r\n            --draft \\\r\n            --title \"spt ${GITHUB_REF_NAME}\" \\\r\n            --notes-file dist/RELEASE_NOTES.md \\\r\n            dist/spt-x86_64-* \\\r\n            dist/SHA256SUMS \\\r\n            dist/manifest.schema.json \\\r\n            dist/mock-adapter.zip \\\r\n            dist/spt-docs.tar.gz\r","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.github\\workflows\\release.yml"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":184,"totalBytes":8663,"outputLines":183,"outputBytes":8663,"shownRange":{"start":1,"end":183},"nextOffset":184}}},"isError":false,"timestamp":1784247370248}}
{"type":"message","id":"d780c7a9","parentId":"ffba78f3","timestamp":"2026-07-17T00:16:10.254Z","message":{"role":"toolResult","toolCallId":"call_yqoceELYGyWfa12cH7JqcfXH|fc_099a7567dadef391016a59744a30f4819b90fe39370c32b498","toolName":"read","content":[{"type":"text","text":"90852bf1bfb8832bf33a27ee58541be2618ec592 18d50459c1849d5ed58d427447871f870ff47edd Reavo End <decidel@gmail.com> 1782301160 -0700\tpull: fast-forward\n18d50459c1849d5ed58d427447871f870ff47edd 9e5b33bb3ed1f6ed6766f5f17373e43ce2778932 Reavo End <decidel@gmail.com> 1782405982 -0700\tpull: fast-forward\n9e5b33bb3ed1f6ed6766f5f17373e43ce2778932 a8baa237c666a529459adc5996990686b388faa6 Reavo End <decidel@gmail.com> 1782650984 -0700\tpull: fast-forward\na8baa237c666a529459adc5996990686b388faa6 461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 Reavo End <decidel@gmail.com> 1782848574 -0700\tpull: fast-forward\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 aebabaa2f7dae538c25fa8c21ab7c9edbc643114 Reavo End <decidel@gmail.com> 1782943485 -0700\tpull --ff-only origin main: Fast-forward\naebabaa2f7dae538c25fa8c21ab7c9edbc643114 9bae13e4136d22f4b5d5d6757b6f206390bfba6a Reavo End <decidel@gmail.com> 1783070613 -0700\treset: moving to origin/main\n9bae13e4136d22f4b5d5d6757b6f206390bfba6a 289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 Reavo End <decidel@gmail.com> 1783121597 -0700\treset: moving to origin/main\n289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 188fe1a9f50a8bbc3234263bb16662a17b51a94e Reavo End <decidel@gmail.com> 1783149421 -0700\treset: moving to origin/main\n188fe1a9f50a8bbc3234263bb16662a17b51a94e 6f668c246e0f2deb3066a141b2311750feaa6f4b Reavo End <decidel@gmail.com> 1783158698 -0700\tcommit: docs(f030): PSYCHE-EPHEMERAL design — per-event driver, residency retirement\n6f668c246e0f2deb3066a141b2311750feaa6f4b 188fe1a9f50a8bbc3234263bb16662a17b51a94e Reavo End <decidel@gmail.com> 1783158727 -0700\tbranch: Reset to origin/main\n188fe1a9f50a8bbc3234263bb16662a17b51a94e c01fe6ee29c2a19c4a6ebc72f11c471cab7d13d0 Reavo End <decidel@gmail.com> 1783220653 -0700\tpull --ff-only origin main: Fast-forward\nc01fe6ee29c2a19c4a6ebc72f11c471cab7d13d0 17a5f9137a14b3946db643fd8c67ba44ee3a9305 Reavo End <decidel@gmail.com> 1783220703 -0700\tcommit: docs(changelog): scrub adapter/harness-specific terms from v0.24.0 + v0.25.0 notes (post-publish, docs-only)\n17a5f9137a14b3946db643fd8c67ba44ee3a9305 06cea5cde193b7f0307a0334de0917ea7bd7742a Reavo End <decidel@gmail.com> 1783220788 -0700\tcherry-pick: docs(remote-truth): file F-5 adapter-floor-unenforced finding + F-6 post-v0.25.0 cleanup riders\n06cea5cde193b7f0307a0334de0917ea7bd7742a b8d8a2a9bab9ec77407992bcd3f6713e3679fb89 Reavo End <decidel@gmail.com> 1783325758 -0700\tpull --ff-only origin main: Fast-forward\nb8d8a2a9bab9ec77407992bcd3f6713e3679fb89 1dcfcfab4168a40e3555aa8f792a152ea5569798 Reavo End <decidel@gmail.com> 1783332033 -0700\tpull --ff-only origin main -q: Fast-forward\n1dcfcfab4168a40e3555aa8f792a152ea5569798 b78bf4295c370e052d8bff8f0b0fbcc457cf1062 Reavo End <decidel@gmail.com> 1783332755 -0700\tpull --ff-only origin main: Fast-forward\nb78bf4295c370e052d8bff8f0b0fbcc457cf1062 247333cc6a2b714b6f6bd2930d29ec9972e28ce3 Reavo End <decidel@gmail.com> 1783334503 -0700\tcommit: docs(worker-truth): WORKER-TRUTH triage — worker-perch lifecycle + visibility work order, 6 REQ seeds registered\n247333cc6a2b714b6f6bd2930d29ec9972e28ce3 7a59e3babb83babe043b3ec34b454109ac8b0518 Reavo End <decidel@gmail.com> 1783344151 -0700\tpull --ff-only origin main -q: Fast-forward\n7a59e3babb83babe043b3ec34b454109ac8b0518 cb4a5ab18697777999bb38a31e241240834dbe13 Reavo End <decidel@gmail.com> 1783351130 -0700\tpull --ff-only origin main -q: Fast-forward\ncb4a5ab18697777999bb38a31e241240834dbe13 47efdaf63a4bee32e52b8a46aba68540cd786f89 Reavo End <decidel@gmail.com> 1783377386 -0700\tpull --ff-only: Fast-forward\n47efdaf63a4bee32e52b8a46aba68540cd786f89 0639b12864be5325469ebabddf609c78f085db3d Reavo End <decidel@gmail.com> 1783390736 -0700\tpull --ff-only origin main -q: Fast-forward\n0639b12864be5325469ebabddf609c78f085db3d 44062189d9b32086690f0cdcf2c32cdbd7ecbfc8 Reavo End <decidel@gmail.com> 1783390883 -0700\tpull --ff-only: Fast-forward\n44062189d9b32086690f0cdcf2c32cdbd7ecbfc8 ab970fedac62d2bd2786af17b0174a82d166d0fa Reavo End <decidel@gmail.com> 1783413911 -0700\tcommit: docs(triage): LIFECYCLE-TRUTH milestone JIT plan — seeds #5-#10, 6 waves, roots pinned\nab970fedac62d2bd2786af17b0174a82d166d0fa 907dd2d710131c210f2543a997ec48b3e82835db Reavo End <decidel@gmail.com> 1783414138 -0700\tcommit: docs(triage): LIFECYCLE-TRUTH +REQ-IDLE-PARKED-DELIVERY — QUEUED-to-parked-idle sleeps (live repro during dispatch)\n907dd2d710131c210f2543a997ec48b3e82835db d349829629b0c01fb39aaa65a109b5ac5066a7ea Reavo End <decidel@gmail.com> 1783508366 -0700\tpull origin main -q: Fast-forward\nd349829629b0c01fb39aaa65a109b5ac5066a7ea 738eb289bccc4c1557f2d795ff8da9c05903dfa2 Reavo End <decidel@gmail.com> 1783548796 -0700\tpull origin main -q: Fast-forward\n738eb289bccc4c1557f2d795ff8da9c05903dfa2 0f0bc6e75d00363bda0b4dfdd9eb63e96424ee7b Reavo End <decidel@gmail.com> 1783548796 -0700\tcommit: docs(musl-tier): W1 dispatch ruling — target-triple centralization\n0f0bc6e75d00363bda0b4dfdd9eb63e96424ee7b 7a3ee6ec492f79b7a8abc1177bfa1bd66c809c90 Reavo End <decidel@gmail.com> 1783556279 -0700\tpull --ff-only origin main: Fast-forward\n7a3ee6ec492f79b7a8abc1177bfa1bd66c809c90 047db7de57bf9e19e6f751d1bc4a6b30b1df1b17 Reavo End <decidel@gmail.com> 1783556428 -0700\tcommit: docs(musl-tier): W3 dispatch ruling — CI musl build + signed release + self-update E2E\n047db7de57bf9e19e6f751d1bc4a6b30b1df1b17 ff7fe9947e38d87b400e981743568d404036a449 Reavo End <decidel@gmail.com> 1783558608 -0700\tcherry-pick: docs(musl-tier): W3 gate PASS + release handoff to deployah\nff7fe9947e38d87b400e981743568d404036a449 c85e2a85ffb7e408d7f22a5c5fe952dc58a023c1 Reavo End <decidel@gmail.com> 1783561384 -0700\tcommit: triage(pump-truth): scope peer-pump offline-peer resilience — queue REQ-PUMP-DIAL-FASTFAIL + REQ-PUMP-PEER-ISOLATION\nc85e2a85ffb7e408d7f22a5c5fe952dc58a023c1 91460a74c53aaa53b0f967ae4338f1b485791aaa Reavo End <decidel@gmail.com> 1783561866 -0700\tcommit: triage(pump-truth): add 3-way root-confirmation gate — todlando+deployah+doyle agree before fix\n91460a74c53aaa53b0f967ae4338f1b485791aaa 9c4e69414a4c10964d1f40417907da497a771d84 Reavo End <decidel@gmail.com> 1783564372 -0700\tmerge origin/main: Fast-forward\n9c4e69414a4c10964d1f40417907da497a771d84 d806932bbe69c388e2ae7eb94b22a61757755d71 Reavo End <decidel@gmail.com> 1783564831 -0700\tcommit: docs(pump-truth): W2 dispatch ruling — rule the 5 async-model forks (REQ-PUMP-PEER-ISOLATION)\nd806932bbe69c388e2ae7eb94b22a61757755d71 6767a4dc5f8748fd37a378b164152fe4e711a82b Reavo End <decidel@gmail.com> 1783564831 -0700\tcommit: triage(f-032): register REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE (perri field finding — commune-ingest data-loss)\n6767a4dc5f8748fd37a378b164152fe4e711a82b 33212298616bc7e44dbc875ae6f05ecc0f02375a Reavo End <decidel@gmail.com> 1783565193 -0700\tcommit: docs(pump-w2): W2 design — per-peer isolation on the D4c presence seam\n33212298616bc7e44dbc875ae6f05ecc0f02375a 6767a4dc5f8748fd37a378b164152fe4e711a82b Reavo End <decidel@gmail.com> 1783565345 -0700\tbranch: Reset to origin/main\n6767a4dc5f8748fd37a378b164152fe4e711a82b ca2038bdf04c632e12ff63e304e5df0569c16829 Reavo End <decidel@gmail.com> 1783565680 -0700\tcommit: docs(pump-truth): W2 design-check addendum — APPROVED + Q1-Q3 rulings\nca2038bdf04c632e12ff63e304e5df0569c16829 79db602eb2ab55ee70ecaa398985a0d46832947c Reavo End <decidel@gmail.com> 1783569840 -0700\tcommit: triage(f-033): register REQ-LIVE-AGENT-NO-INJECT-DELIVERY (duplicate inbound to a live_agent)\n79db602eb2ab55ee70ecaa398985a0d46832947c f16e2e0282c48192f01d94f0f87f4267388e511b Reavo End <decidel@gmail.com> 1783572805 -0700\tcommit: docs(pump-truth): W1 root-lock + fix ruling — sync-reply-read poison (not a dial bug)\nf16e2e0282c48192f01d94f0f87f4267388e511b 206884f8b6d91307b1faac1ce7d5943131d4858a Reavo End <decidel@gmail.com> 1783572988 -0700\tcommit: docs(pump-truth): round-2 empirical lock — it's the UPDATE leg (propagate.rs:373-375), not sync\n206884f8b6d91307b1faac1ce7d5943131d4858a 134f0fea01d9fe606ee08b9db0013274ade7862c Reavo End <decidel@gmail.com> 1783573138 -0700\tcommit: triage(pump-truth): mark REQ-PUMP-DIAL-FASTFAIL re-scoped — dial exonerated, fix is the reply-read poison\n134f0fea01d9fe606ee08b9db0013274ade7862c 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783576224 -0700\tcommit: triage(f-034): register 3 core legs for /sptc:live bringup cascade (perri/hertz field finding)\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740438 -0700\tpull origin main: Fast-forward\na6ff50193646ddf8c68deee300ce535565297840 c9586d1734ef2bed887d5725265940cf567228ba Reavo End <decidel@gmail.com> 1783743757 -0700\tpull --ff-only: Fast-forward\nc9586d1734ef2bed887d5725265940cf567228ba 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784083738 -0700\tcommit: triage(the-forkening): private fork + gh distribution + local docs milestone — ADR-0036 (supersedes ADR-0014), 9 REQs registry-first, CONTEXT amendments\n3cb54b4df8a131b99bdd11447ec8fc852657cf6c 832b982069e4e473db79637d894e528c9018d3ef Reavo End <decidel@gmail.com> 1784093961 -0700\tpull origin main: Fast-forward\n832b982069e4e473db79637d894e528c9018d3ef f513b5d23d4d17eade93dac7f2f9e80a587d3f40 Reavo End <decidel@gmail.com> 1784110030 -0700\tpull origin main: Fast-forward\nf513b5d23d4d17eade93dac7f2f9e80a587d3f40 cff7c1f5b1f6c072e794a38127835df88f960ee4 Reavo End <decidel@gmail.com> 1784114266 -0700\tpull origin main: Fast-forward\ncff7c1f5b1f6c072e794a38127835df88f960ee4 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119445 -0700\tpull origin main: Fast-forward\ne0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 ca6386e97034f5a29ee97a6691af9d66fb3d1665 Reavo End <decidel@gmail.com> 1784122295 -0700\tpull origin main: Fast-forward\nca6386e97034f5a29ee97a6691af9d66fb3d1665 e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784162001 -0700\tcommit: triage(project-index): milestone JIT — ADR-0037 + 5 REQs registry-first\ne5f053fd130f7eed1e40e927c0bab87d88bb007a e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784166404 -0700\tpull --ff-only origin main: Fast-forward\ne4573279c6389023f5fc4f61ad88e27a12202a6f e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784170613 -0700\tpull --ff-only origin main: Fast-forward\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188142 -0700\tpull --ff-only origin main: Fast-forward\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tbranch: Reset to origin/main\n21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\nb7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700\tmerge origin/main: Fast-forward\nd41ceaab197af6ebf1da842400dff3a6518084bb 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784239433 -0700\tcherry-pick: triage(mesh-recovery): milestone JIT — ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first\n0bb37f352d871f288d3c1ab5af6133020d57c674 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784246971 -0700\tmerge origin/main: Fast-forward\n"}],"details":{"displayContent":{"text":"90852bf1bfb8832bf33a27ee58541be2618ec592 18d50459c1849d5ed58d427447871f870ff47edd Reavo End <decidel@gmail.com> 1782301160 -0700\tpull: fast-forward\n18d50459c1849d5ed58d427447871f870ff47edd 9e5b33bb3ed1f6ed6766f5f17373e43ce2778932 Reavo End <decidel@gmail.com> 1782405982 -0700\tpull: fast-forward\n9e5b33bb3ed1f6ed6766f5f17373e43ce2778932 a8baa237c666a529459adc5996990686b388faa6 Reavo End <decidel@gmail.com> 1782650984 -0700\tpull: fast-forward\na8baa237c666a529459adc5996990686b388faa6 461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 Reavo End <decidel@gmail.com> 1782848574 -0700\tpull: fast-forward\n461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 aebabaa2f7dae538c25fa8c21ab7c9edbc643114 Reavo End <decidel@gmail.com> 1782943485 -0700\tpull --ff-only origin main: Fast-forward\naebabaa2f7dae538c25fa8c21ab7c9edbc643114 9bae13e4136d22f4b5d5d6757b6f206390bfba6a Reavo End <decidel@gmail.com> 1783070613 -0700\treset: moving to origin/main\n9bae13e4136d22f4b5d5d6757b6f206390bfba6a 289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 Reavo End <decidel@gmail.com> 1783121597 -0700\treset: moving to origin/main\n289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 188fe1a9f50a8bbc3234263bb16662a17b51a94e Reavo End <decidel@gmail.com> 1783149421 -0700\treset: moving to origin/main\n188fe1a9f50a8bbc3234263bb16662a17b51a94e 6f668c246e0f2deb3066a141b2311750feaa6f4b Reavo End <decidel@gmail.com> 1783158698 -0700\tcommit: docs(f030): PSYCHE-EPHEMERAL design — per-event driver, residency retirement\n6f668c246e0f2deb3066a141b2311750feaa6f4b 188fe1a9f50a8bbc3234263bb16662a17b51a94e Reavo End <decidel@gmail.com> 1783158727 -0700\tbranch: Reset to origin/main\n188fe1a9f50a8bbc3234263bb16662a17b51a94e c01fe6ee29c2a19c4a6ebc72f11c471cab7d13d0 Reavo End <decidel@gmail.com> 1783220653 -0700\tpull --ff-only origin main: Fast-forward\nc01fe6ee29c2a19c4a6ebc72f11c471cab7d13d0 17a5f9137a14b3946db643fd8c67ba44ee3a9305 Reavo End <decidel@gmail.com> 1783220703 -0700\tcommit: docs(changelog): scrub adapter/harness-specific terms from v0.24.0 + v0.25.0 notes (post-publish, docs-only)\n17a5f9137a14b3946db643fd8c67ba44ee3a9305 06cea5cde193b7f0307a0334de0917ea7bd7742a Reavo End <decidel@gmail.com> 1783220788 -0700\tcherry-pick: docs(remote-truth): file F-5 adapter-floor-unenforced finding + F-6 post-v0.25.0 cleanup riders\n06cea5cde193b7f0307a0334de0917ea7bd7742a b8d8a2a9bab9ec77407992bcd3f6713e3679fb89 Reavo End <decidel@gmail.com> 1783325758 -0700\tpull --ff-only origin main: Fast-forward\nb8d8a2a9bab9ec77407992bcd3f6713e3679fb89 1dcfcfab4168a40e3555aa8f792a152ea5569798 Reavo End <decidel@gmail.com> 1783332033 -0700\tpull --ff-only origin main -q: Fast-forward\n1dcfcfab4168a40e3555aa8f792a152ea5569798 b78bf4295c370e052d8bff8f0b0fbcc457cf1062 Reavo End <decidel@gmail.com> 1783332755 -0700\tpull --ff-only origin main: Fast-forward\nb78bf4295c370e052d8bff8f0b0fbcc457cf1062 247333cc6a2b714b6f6bd2930d29ec9972e28ce3 Reavo End <decidel@gmail.com> 1783334503 -0700\tcommit: docs(worker-truth): WORKER-TRUTH triage — worker-perch lifecycle + visibility work order, 6 REQ seeds registered\n247333cc6a2b714b6f6bd2930d29ec9972e28ce3 7a59e3babb83babe043b3ec34b454109ac8b0518 Reavo End <decidel@gmail.com> 1783344151 -0700\tpull --ff-only origin main -q: Fast-forward\n7a59e3babb83babe043b3ec34b454109ac8b0518 cb4a5ab18697777999bb38a31e241240834dbe13 Reavo End <decidel@gmail.com> 1783351130 -0700\tpull --ff-only origin main -q: Fast-forward\ncb4a5ab18697777999bb38a31e241240834dbe13 47efdaf63a4bee32e52b8a46aba68540cd786f89 Reavo End <decidel@gmail.com> 1783377386 -0700\tpull --ff-only: Fast-forward\n47efdaf63a4bee32e52b8a46aba68540cd786f89 0639b12864be5325469ebabddf609c78f085db3d Reavo End <decidel@gmail.com> 1783390736 -0700\tpull --ff-only origin main -q: Fast-forward\n0639b12864be5325469ebabddf609c78f085db3d 44062189d9b32086690f0cdcf2c32cdbd7ecbfc8 Reavo End <decidel@gmail.com> 1783390883 -0700\tpull --ff-only: Fast-forward\n44062189d9b32086690f0cdcf2c32cdbd7ecbfc8 ab970fedac62d2bd2786af17b0174a82d166d0fa Reavo End <decidel@gmail.com> 1783413911 -0700\tcommit: docs(triage): LIFECYCLE-TRUTH milestone JIT plan — seeds #5-#10, 6 waves, roots pinned\nab970fedac62d2bd2786af17b0174a82d166d0fa 907dd2d710131c210f2543a997ec48b3e82835db Reavo End <decidel@gmail.com> 1783414138 -0700\tcommit: docs(triage): LIFECYCLE-TRUTH +REQ-IDLE-PARKED-DELIVERY — QUEUED-to-parked-idle sleeps (live repro during dispatch)\n907dd2d710131c210f2543a997ec48b3e82835db d349829629b0c01fb39aaa65a109b5ac5066a7ea Reavo End <decidel@gmail.com> 1783508366 -0700\tpull origin main -q: Fast-forward\nd349829629b0c01fb39aaa65a109b5ac5066a7ea 738eb289bccc4c1557f2d795ff8da9c05903dfa2 Reavo End <decidel@gmail.com> 1783548796 -0700\tpull origin main -q: Fast-forward\n738eb289bccc4c1557f2d795ff8da9c05903dfa2 0f0bc6e75d00363bda0b4dfdd9eb63e96424ee7b Reavo End <decidel@gmail.com> 1783548796 -0700\tcommit: docs(musl-tier): W1 dispatch ruling — target-triple centralization\n0f0bc6e75d00363bda0b4dfdd9eb63e96424ee7b 7a3ee6ec492f79b7a8abc1177bfa1bd66c809c90 Reavo End <decidel@gmail.com> 1783556279 -0700\tpull --ff-only origin main: Fast-forward\n7a3ee6ec492f79b7a8abc1177bfa1bd66c809c90 047db7de57bf9e19e6f751d1bc4a6b30b1df1b17 Reavo End <decidel@gmail.com> 1783556428 -0700\tcommit: docs(musl-tier): W3 dispatch ruling — CI musl build + signed release + self-update E2E\n047db7de57bf9e19e6f751d1bc4a6b30b1df1b17 ff7fe9947e38d87b400e981743568d404036a449 Reavo End <decidel@gmail.com> 1783558608 -0700\tcherry-pick: docs(musl-tier): W3 gate PASS + release handoff to deployah\nff7fe9947e38d87b400e981743568d404036a449 c85e2a85ffb7e408d7f22a5c5fe952dc58a023c1 Reavo End <decidel@gmail.com> 1783561384 -0700\tcommit: triage(pump-truth): scope peer-pump offline-peer resilience — queue REQ-PUMP-DIAL-FASTFAIL + REQ-PUMP-PEER-ISOLATION\nc85e2a85ffb7e408d7f22a5c5fe952dc58a023c1 91460a74c53aaa53b0f967ae4338f1b485791aaa Reavo End <decidel@gmail.com> 1783561866 -0700\tcommit: triage(pump-truth): add 3-way root-confirmation gate — todlando+deployah+doyle agree before fix\n91460a74c53aaa53b0f967ae4338f1b485791aaa 9c4e69414a4c10964d1f40417907da497a771d84 Reavo End <decidel@gmail.com> 1783564372 -0700\tmerge origin/main: Fast-forward\n9c4e69414a4c10964d1f40417907da497a771d84 d806932bbe69c388e2ae7eb94b22a61757755d71 Reavo End <decidel@gmail.com> 1783564831 -0700\tcommit: docs(pump-truth): W2 dispatch ruling — rule the 5 async-model forks (REQ-PUMP-PEER-ISOLATION)\nd806932bbe69c388e2ae7eb94b22a61757755d71 6767a4dc5f8748fd37a378b164152fe4e711a82b Reavo End <decidel@gmail.com> 1783564831 -0700\tcommit: triage(f-032): register REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE (perri field finding — commune-ingest data-loss)\n6767a4dc5f8748fd37a378b164152fe4e711a82b 33212298616bc7e44dbc875ae6f05ecc0f02375a Reavo End <decidel@gmail.com> 1783565193 -0700\tcommit: docs(pump-w2): W2 design — per-peer isolation on the D4c presence seam\n33212298616bc7e44dbc875ae6f05ecc0f02375a 6767a4dc5f8748fd37a378b164152fe4e711a82b Reavo End <decidel@gmail.com> 1783565345 -0700\tbranch: Reset to origin/main\n6767a4dc5f8748fd37a378b164152fe4e711a82b ca2038bdf04c632e12ff63e304e5df0569c16829 Reavo End <decidel@gmail.com> 1783565680 -0700\tcommit: docs(pump-truth): W2 design-check addendum — APPROVED + Q1-Q3 rulings\nca2038bdf04c632e12ff63e304e5df0569c16829 79db602eb2ab55ee70ecaa398985a0d46832947c Reavo End <decidel@gmail.com> 1783569840 -0700\tcommit: triage(f-033): register REQ-LIVE-AGENT-NO-INJECT-DELIVERY (duplicate inbound to a live_agent)\n79db602eb2ab55ee70ecaa398985a0d46832947c f16e2e0282c48192f01d94f0f87f4267388e511b Reavo End <decidel@gmail.com> 1783572805 -0700\tcommit: docs(pump-truth): W1 root-lock + fix ruling — sync-reply-read poison (not a dial bug)\nf16e2e0282c48192f01d94f0f87f4267388e511b 206884f8b6d91307b1faac1ce7d5943131d4858a Reavo End <decidel@gmail.com> 1783572988 -0700\tcommit: docs(pump-truth): round-2 empirical lock — it's the UPDATE leg (propagate.rs:373-375), not sync\n206884f8b6d91307b1faac1ce7d5943131d4858a 134f0fea01d9fe606ee08b9db0013274ade7862c Reavo End <decidel@gmail.com> 1783573138 -0700\tcommit: triage(pump-truth): mark REQ-PUMP-DIAL-FASTFAIL re-scoped — dial exonerated, fix is the reply-read poison\n134f0fea01d9fe606ee08b9db0013274ade7862c 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783576224 -0700\tcommit: triage(f-034): register 3 core legs for /sptc:live bringup cascade (perri/hertz field finding)\n55556482ebca6a2d3c6c453edcbc9b261eb27c15 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740438 -0700\tpull origin main: Fast-forward\na6ff50193646ddf8c68deee300ce535565297840 c9586d1734ef2bed887d5725265940cf567228ba Reavo End <decidel@gmail.com> 1783743757 -0700\tpull --ff-only: Fast-forward\nc9586d1734ef2bed887d5725265940cf567228ba 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784083738 -0700\tcommit: triage(the-forkening): private fork + gh distribution + local docs milestone — ADR-0036 (supersedes ADR-0014), 9 REQs registry-first, CONTEXT amendments\n3cb54b4df8a131b99bdd11447ec8fc852657cf6c 832b982069e4e473db79637d894e528c9018d3ef Reavo End <decidel@gmail.com> 1784093961 -0700\tpull origin main: Fast-forward\n832b982069e4e473db79637d894e528c9018d3ef f513b5d23d4d17eade93dac7f2f9e80a587d3f40 Reavo End <decidel@gmail.com> 1784110030 -0700\tpull origin main: Fast-forward\nf513b5d23d4d17eade93dac7f2f9e80a587d3f40 cff7c1f5b1f6c072e794a38127835df88f960ee4 Reavo End <decidel@gmail.com> 1784114266 -0700\tpull origin main: Fast-forward\ncff7c1f5b1f6c072e794a38127835df88f960ee4 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119445 -0700\tpull origin main: Fast-forward\ne0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 ca6386e97034f5a29ee97a6691af9d66fb3d1665 Reavo End <decidel@gmail.com> 1784122295 -0700\tpull origin main: Fast-forward\nca6386e97034f5a29ee97a6691af9d66fb3d1665 e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784162001 -0700\tcommit: triage(project-index): milestone JIT — ADR-0037 + 5 REQs registry-first\ne5f053fd130f7eed1e40e927c0bab87d88bb007a e4573279c6389023f5fc4f61ad88e27a12202a6f Reavo End <decidel@gmail.com> 1784166404 -0700\tpull --ff-only origin main: Fast-forward\ne4573279c6389023f5fc4f61ad88e27a12202a6f e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784170613 -0700\tpull --ff-only origin main: Fast-forward\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188142 -0700\tpull --ff-only origin main: Fast-forward\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tbranch: Reset to origin/main\n21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\nb7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700\tmerge origin/main: Fast-forward\nd41ceaab197af6ebf1da842400dff3a6518084bb 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784239433 -0700\tcherry-pick: triage(mesh-recovery): milestone JIT — ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first\n0bb37f352d871f288d3c1ab5af6133020d57c674 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784246971 -0700\tmerge origin/main: Fast-forward\n","startLine":119,"lineNumbers":[119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\heads\\main"}}},"isError":false,"timestamp":1784247370253}}
{"type":"message","id":"7b30ce70","parentId":"d780c7a9","timestamp":"2026-07-17T00:16:10.255Z","message":{"role":"toolResult","toolCallId":"call_p3hHPVoJ07g47HScLVhfjUXX|fc_099a7567dadef391016a59744a30d4819b9cc796520014471e","toolName":"read","content":[{"type":"text","text":"21a46d5560819e505c83f0908b7b562d65b7d1ac\n"}],"details":{"displayContent":{"text":"21a46d5560819e505c83f0908b7b562d65b7d1ac\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\tags\\v0.33.0"}}},"isError":false,"timestamp":1784247370255}}
{"type":"message","id":"14419309","parentId":"7b30ce70","timestamp":"2026-07-17T00:16:10.255Z","message":{"role":"toolResult","toolCallId":"call_BAbYAb8tkkFBjpSTxtKkA4Wt|fc_099a7567dadef391016a59744a30c4819ba761b97588dc2a9f","toolName":"read","content":[{"type":"text","text":"b7b074c2509c1015fe341185662b4622d661f274\n"}],"details":{"displayContent":{"text":"b7b074c2509c1015fe341185662b4622d661f274\n","startLine":1,"lineNumbers":[1,2]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\refs\\tags\\v0.34.0"}}},"isError":false,"timestamp":1784247370255}}
{"type":"message","id":"bf34c2de","parentId":"14419309","timestamp":"2026-07-17T00:16:10.262Z","message":{"role":"toolResult","toolCallId":"call_XSQ8A4c49Epbb1qlL3LAXMsQ|fc_099a7567dadef391016a59744a3108819b9d93366535a24fa0","toolName":"grep","content":[{"type":"text","text":"# .git/logs/\n## HEAD\n 361|d24886a110814925c853dc0b12fd16e010a8d92a bc3c1f2e4cce5e5de55bd75602647d3cab84d428 Reavo End <decidel@gmail.com> 1780973376 -0700\tcommit: docs(changelog): daemon-lifecycle UX + concurrent --nodes probes (Unreleased)\n*362|bc3c1f2e4cce5e5de55bd75602647d3cab84d428 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974008 -0700\tcommit: release: v0.3.0\n 363|35d1672fdf999a4fc6e58fcd04ff5c953452beb0 4fa29498d2552f277e918219f74d9c75183a6921 Reavo End <decidel@gmail.com> 1780974018 -0700\tcheckout: moving from dev-freeform to main\n 364|4fa29498d2552f277e918219f74d9c75183a6921 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974019 -0700\tmerge dev-freeform: Fast-forward\n 365|35d1672fdf999a4fc6e58fcd04ff5c953452beb0 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974701 -0700\tcheckout: moving from main to dev-freeform\n...\n 379|c44e62209cc5a4c6c6ab9d6cfb247728e1974d9b 63fbbee31235a5107d2ccdb364efd77d16af2e6f Reavo End <decidel@gmail.com> 1780980823 -0700\tcommit: feat(update): `spt update fetch` — origin-source bootstrap from GitHub (REQ-UPD-7)\n*380|63fbbee31235a5107d2ccdb364efd77d16af2e6f 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780981240 -0700\tcommit: release: v0.3.1\n 381|516465c1cbf959ed89c77464b38e9c4ed5142ad9 9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a Reavo End <decidel@gmail.com> 1780981240 -0700\tcheckout: moving from dev-freeform to main\n 382|9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780981240 -0700\tmerge dev-freeform: Fast-forward\n 383|516465c1cbf959ed89c77464b38e9c4ed5142ad9 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780982347 -0700\tcheckout: moving from main to dev-freeform\n...\n 392|4e774a391077a9bdce29906aaed6de804da5de1b a65b508bc66eca351480ae0ea2d875369fb07629 Reavo End <decidel@gmail.com> 1780998624 -0700\tcommit: feat(update): platform-safe `spt update fetch` + apply platform-guard (REQ-UPD-8)\n*393|a65b508bc66eca351480ae0ea2d875369fb07629 cabdc614fc0416e3a57de0a21b229db485089320 Reavo End <decidel@gmail.com> 1780998629 -0700\tcommit: release: prep v0.3.2 (version bump + CHANGELOG)\n 394|cabdc614fc0416e3a57de0a21b229db485089320 4e774a391077a9bdce29906aaed6de804da5de1b Reavo End <decidel@gmail.com> 1780999401 -0700\tcheckout: moving from dev-freeform to main\n 395|4e774a391077a9bdce29906aaed6de804da5de1b cabdc614fc0416e3a57de0a21b229db485089320 Reavo End <decidel@gmail.com> 1780999401 -0700\tmerge dev-freeform: Fast-forward\n 396|cabdc614fc0416e3a57de0a21b229db485089320 44497f466c89b104de2a10816c30f5b68c8fd28a Reavo End <decidel@gmail.com> 1781052390 -0700\tcommit: docs(restoration): broker/brain split regression — design + ADR-0018 + hazards + REQ mints\n...\n 481|be626bf83329bfb90fef3f2fee15aab87199ce13 f69e0cd8e11c75f18eef74d4c939a5c21ebf1b96 Reavo End <decidel@gmail.com> 1781172899 -0700\tcommit: test(daemon): in-place-rename respawn int E2E (REQ-HAZARD-BRAIN-RESPAWN-PATH int)\n*482|f69e0cd8e11c75f18eef74d4c939a5c21ebf1b96 9fad2c1ade9fbb289216de0590fe3629abc3af2b Reavo End <decidel@gmail.com> 1781173319 -0700\tcommit: release: v0.4.2 — bump + changelog (Linux brain-respawn-path fix)\n 483|9fad2c1ade9fbb289216de0590fe3629abc3af2b 038257bb9c4a405244a6724beaf2ececb30e7111 Reavo End <decidel@gmail.com> 1781173990 -0700\tcheckout: moving from fix/v0.4.2-brain-respawn-path to main\n 484|038257bb9c4a405244a6724beaf2ececb30e7111 725aebf647f8ebe4415fcfd2274488b41cc561f0 Reavo End <decidel@gmail.com> 1781173992 -0700\tpull --ff-only origin main: Fast-forward\n 485|725aebf647f8ebe4415fcfd2274488b41cc561f0 725aebf647f8ebe4415fcfd2274488b41cc561f0 Reavo End <decidel@gmail.com> 1781174801 -0700\tcheckout: moving from main to main\n...\n 594|e08cea0dd604d378c77233876ff9f9d895d6ff7e 30d2a92a0daa1631af566f3196bf3e07e9a152ee Reavo End <decidel@gmail.com> 1781520755 -0700\tpull --ff-only origin main: Fast-forward\n*595|30d2a92a0daa1631af566f3196bf3e07e9a152ee cb0560052c7392d386d9e68db359bbbf3138dcfe Reavo End <decidel@gmail.com> 1781520973 -0700\tcommit: release: v0.7.1 — <EVENT> envelope everywhere + confident update-apply message\n 596|cb0560052c7392d386d9e68db359bbbf3138dcfe cb0560052c7392d386d9e68db359bbbf3138dcfe Reavo End <decidel@gmail.com> 1781524142 -0700\tcheckout: moving from main to m11-shell-substrate\n 597|cb0560052c7392d386d9e68db359bbbf3138dcfe 5251d161482051a9a33fdc2360b8abc5027dc42f Reavo End <decidel@gmail.com> 1781524297 -0700\tcommit: M11-T0: shell-ownership audit + register substrate-extension reqs\n 598|5251d161482051a9a33fdc2360b8abc5027dc42f 5251d161482051a9a33fdc2360b8abc5027dc42f Reavo End <decidel@gmail.com> 1781524434 -0700\tcheckout: moving from m11-shell-substrate to docs/bind-intrinsic-auth-trust-model\n...\n 714|a461dc47963b4790d839eec123a72f24b11c17fb 54f167ca81fb9532fdfb29f9305e279e3d0c5f91 Reavo End <decidel@gmail.com> 1781843853 -0700\tcommit: v0.12.1 docs: regen CLI reference after help-md merge (help strips MD markers)\n*715|54f167ca81fb9532fdfb29f9305e279e3d0c5f91 6473fe7485097ed899e77bfa4f71e9a2707cd554 Reavo End <decidel@gmail.com> 1781846220 -0700\tcommit: release: v0.12.1 — real-harness lifecycle fixes + CLI polish\n 716|6473fe7485097ed899e77bfa4f71e9a2707cd554 185eaeeb77026473a12d7b74432c81a53a0ee43d Reavo End <decidel@gmail.com> 1781846902 -0700\tcommit: fix(ci): test-only — Linux dead-trait gate + breakaway test skips a runner-confounded escape\n 717|185eaeeb77026473a12d7b74432c81a53a0ee43d 86f20ac831033a20bd5e096b1c318a75249179f3 Reavo End <decidel@gmail.com> 1781848891 -0700\tcommit: fix(ci): job_escape_e2e reaps the WHOLE WMI daemon tree (broker + brain)\n 718|86f20ac831033a20bd5e096b1c318a75249179f3 86f20ac831033a20bd5e096b1c318a75249179f3 Reavo End <decidel@gmail.com> 1781851530 -0700\tcheckout: moving from v0.12.1-lifecycle to v0.12.2-cli-fixes\n...\n 972|160d8f36eaacda158a62836869d1b0c923e2ea54 b67d23d3a0101662e8e5c3682070851e82dbf42b Reavo End <decidel@gmail.com> 1782899886 -0700\tcommit (amend): test(attach): #4×#6 intersection — cross-node cold attach to an alt-screen session gets the clean repaint\n*973|b67d23d3a0101662e8e5c3682070851e82dbf42b d819a522816b569ada0165e9481241089db573fc Reavo End <decidel@gmail.com> 1782901046 -0700\tcommit: release: v0.19.0 — counter-38 field-bug milestone\n 974|d819a522816b569ada0165e9481241089db573fc a5129cdcb267344731d77852a5c4ac731c9d5251 Reavo End <decidel@gmail.com> 1782905044 -0700\tcommit: test(wedge): env-gate the paste-wedge backpressure REPRODUCTION assert (kitsubito fast-drain)\n 975|a5129cdcb267344731d77852a5c4ac731c9d5251 a5129cdcb267344731d77852a5c4ac731c9d5251 Reavo End <decidel@gmail.com> 1782941879 -0700\tcheckout: moving from field-bug-milestone to docs/digest-fetcher-published-prose\n 976|a5129cdcb267344731d77852a5c4ac731c9d5251 296a0a97f72ad93631f655c92438fca1301fbdbd Reavo End <decidel@gmail.com> 1782941879 -0700\tcommit: docs(manifest): document [digest] fetcher strategy on the published surface\n...\n 985|043a6d165d365f6a795934edada1ec2d502c56be f4b43c2dca4cc7daa7f4252db53dc113cf516b35 Reavo End <decidel@gmail.com> 1782958383 -0700\tcommit (amend): fix(info): serialize all info.json writers under the per-perch lock (lost-update)\n*986|f4b43c2dca4cc7daa7f4252db53dc113cf516b35 827697ba118bb7ad69265c6c263b9053ab382ccc Reavo End <decidel@gmail.com> 1782959744 -0700\tcommit: release: v0.19.1 — counter-39 field-bug milestone\n 987|827697ba118bb7ad69265c6c263b9053ab382ccc fc92ac2826d1aba81175306da7f8bbc45f2e431b Reavo End <decidel@gmail.com> 1782974143 -0700\tcheckout: moving from counter39-field-bugs to endpoint-list-redesign\n 988|fc92ac2826d1aba81175306da7f8bbc45f2e431b 1fe3c51213df9c20f40be94f8213863ccaf03bac Reavo End <decidel@gmail.com> 1782974218 -0700\tcommit: docs(list-redesign): REQ stubs + glossary ruling for node-grouped endpoint list\n 989|1fe3c51213df9c20f40be94f8213863ccaf03bac 1fe3c51213df9c20f40be94f8213863ccaf03bac Reavo End <decidel@gmail.com> 1782974407 -0700\tcheckout: moving from endpoint-list-redesign to endpoint-list-redesign\n...\n 1013|9731126874812af3ea51ef792347626687418ec8 c65441caae59e200363ca86dd9fba506eb0b5bcb Reavo End <decidel@gmail.com> 1782987421 -0700\tcommit: feat(f024): cli-origin stamp — never a blank sender\n*1014|c65441caae59e200363ca86dd9fba506eb0b5bcb 8a4e4e54da442c17800cd6ba7f5b8814454b4b75 Reavo End <decidel@gmail.com> 1782988548 -0700\tcommit: release: v0.20.0 — cross-node WAN-idle delivery + fixes\n 1015|8a4e4e54da442c17800cd6ba7f5b8814454b4b75 3043f4e5f4c6d55301f30fb48afc2340f82ae77d Reavo End <decidel@gmail.com> 1783033981 -0700\tcheckout: moving from f023-wan-idle-delivery to f025-listredesign\n 1016|3043f4e5f4c6d55301f30fb48afc2340f82ae77d 2f79062b86e7a0e254566dbe6ae440e6c89de8d9 Reavo End <decidel@gmail.com> 1783034129 -0700\tcommit: docs(f025): mint 3 update-apply honesty REQs (F-025, stages [])\n 1017|2f79062b86e7a0e254566dbe6ae440e6c89de8d9 47576deecd83570acf277ea7986cc07004310763 Reavo End <decidel@gmail.com> 1783034853 -0700\tcommit: feat(f025): surface running broker image version — F-025 item 1a\n...\n 1031|04a3622c0880501a8f4861b048052d0581593a28 b2b1f8639dd13409b1e9902a4740aef9fc356b2e Reavo End <decidel@gmail.com> 1783041555 -0700\tcommit: docs(changelog): v0.21.0 — endpoint-list redesign + update-apply honesty\n*1032|b2b1f8639dd13409b1e9902a4740aef9fc356b2e 65261ad9f495896272c25fc0bcd6d7417e9aed32 Reavo End <decidel@gmail.com> 1783041732 -0700\tcommit: release: v0.21.0 — endpoint-list redesign + update-apply honesty\n 1033|65261ad9f495896272c25fc0bcd6d7417e9aed32 5694d686a23d57a763e12d66d114ac958599ad23 Reavo End <decidel@gmail.com> 1783041912 -0700\tcommit: docs(changelog): add concurrent-store-init Fixed bullet (doyle gate)\n 1034|5694d686a23d57a763e12d66d114ac958599ad23 e96f7d0a7f9dba585a1c2d3f70e4d3fd7e4b212f Reavo End <decidel@gmail.com> 1783047440 -0700\tcheckout: moving from f025-listredesign to f026-pickertruth\n 1035|e96f7d0a7f9dba585a1c2d3f70e4d3fd7e4b212f 5adc527ecb96868d1a26ed02f312d6efba87a7c7 Reavo End <decidel@gmail.com> 1783047644 -0700\tcommit: chore(f026): seed BUILD-F026-PICKERTRUTH — mint 9 REQs + triage doc\n...\n 1050|a390490ca49ee65b91744a2c5a2286f02abb234b a390490ca49ee65b91744a2c5a2286f02abb234b Reavo End <decidel@gmail.com> 1783065398 -0700\tcheckout: moving from f026-pickertruth to f026-pickertruth\n*1051|a390490ca49ee65b91744a2c5a2286f02abb234b 7e29ffcc36ba0bfc3b98ee6dacccf2a0c5a25668 Reavo End <decidel@gmail.com> 1783065544 -0700\tcommit: release: v0.22.0 — picker/presence truth (F-026)\n 1052|7e29ffcc36ba0bfc3b98ee6dacccf2a0c5a25668 ffca259e610631a732c264072f351ce27c98ea07 Reavo End <decidel@gmail.com> 1783066083 -0700\tcommit: fix(f026): SI-1 test — cfg-gate resolve_endpoint_drop_dir_cases paths (Linux abs-path semantics)\n 1053|ffca259e610631a732c264072f351ce27c98ea07 ffca259e610631a732c264072f351ce27c98ea07 Reavo End <decidel@gmail.com> 1783070519 -0700\tcheckout: moving from f026-pickertruth to fix/changelog-0220-wording\n 1054|ffca259e610631a732c264072f351ce27c98ea07 ad3038ab6d73e1378ac4b9290c7db5e4bea1df54 Reavo End <decidel@gmail.com> 1783070519 -0700\tcommit: docs(changelog): correct two v0.22.0 release-note bullets\n...\n 1083|5005a757e3b3a697f6f2ea504605286070699298 5005a757e3b3a697f6f2ea504605286070699298 Reavo End <decidel@gmail.com> 1783116723 -0700\tcheckout: moving from f028-runtruth to f028-runtruth\n*1084|5005a757e3b3a697f6f2ea504605286070699298 0833a485c376d1353dc99b60937af3920a568941 Reavo End <decidel@gmail.com> 1783116909 -0700\tcommit: release: v0.23.0 — RUNTRUTH (F-028)\n 1085|0833a485c376d1353dc99b60937af3920a568941 5341b971030a47dd70c741639c50f33598eb2558 Reavo End <decidel@gmail.com> 1783117097 -0700\tcommit: docs(changelog): v0.23.0 — add two user-facing daemon surfaces (doyle gate)\n 1086|5341b971030a47dd70c741639c50f33598eb2558 9bae13e4136d22f4b5d5d6757b6f206390bfba6a Reavo End <decidel@gmail.com> 1783121596 -0700\tcheckout: moving from f028-runtruth to main\n 1087|9bae13e4136d22f4b5d5d6757b6f206390bfba6a 289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 Reavo End <decidel@gmail.com> 1783121597 -0700\treset: moving to origin/main\n...\n 1108|e7b514cf968027a85e71662329ab8283d5cb2d24 0999cc2f21097fc5798538be29f797ef536a0234 Reavo End <decidel@gmail.com> 1783144036 -0700\tmerge origin/main: Merge made by the 'ort' strategy.\n*1109|0999cc2f21097fc5798538be29f797ef536a0234 f8428a8c814a808d0ffabc18a65f8e7adebf7f51 Reavo End <decidel@gmail.com> 1783144184 -0700\tcommit: release: v0.24.0 — PICKER-POLISH (F-029)\n 1110|f8428a8c814a808d0ffabc18a65f8e7adebf7f51 289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 Reavo End <decidel@gmail.com> 1783149420 -0700\tcheckout: moving from f028-runtruth to main\n 1111|289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 188fe1a9f50a8bbc3234263bb16662a17b51a94e Reavo End <decidel@gmail.com> 1783149421 -0700\treset: moving to origin/main\n 1112|188fe1a9f50a8bbc3234263bb16662a17b51a94e 6f668c246e0f2deb3066a141b2311750feaa6f4b Reavo End <decidel@gmail.com> 1783158698 -0700\tcommit: docs(f030): PSYCHE-EPHEMERAL design — per-event driver, residency retirement\n...\n 1299|fbf5d359186bfcec17fa86f2814ceddcc5ad2652 53a04702912c49a7cfa50bf547526125202d5956 Reavo End <decidel@gmail.com> 1783473974 -0700\tcommit: docs(lifecycle-truth): W6 GATE VERDICT — PASS, milestone COMPLETE (all 6 waves gated)\n*1300|53a04702912c49a7cfa50bf547526125202d5956 a74e5269e0337504363a77744b65e0765537db69 Reavo End <decidel@gmail.com> 1783474761 -0700\tcommit: release: v0.29.0 — LIFECYCLE-TRUTH (bump + changelog)\n 1301|a74e5269e0337504363a77744b65e0765537db69 71907304fffca9ab8e569b72500e281c16c523d0 Reavo End <decidel@gmail.com> 1783476169 -0700\tcommit: fix(ci): n1-gate observation + traceability parse — release v0.29.0 gate-hold\n 1302|71907304fffca9ab8e569b72500e281c16c523d0 a907937794efd479d6aaabc6fe6f23ca2a607fe0 Reavo End <decidel@gmail.com> 1783481698 -0700\tcommit: docs(changelog): correct v0.29.0 notes per operator field review\n 1303|a907937794efd479d6aaabc6fe6f23ca2a607fe0 8aac39b7a20edb84101f05ad9514800592fd81c8 Reavo End <decidel@gmail.com> 1783504443 -0700\tcheckout: moving from lifecycle-truth to fix/inject-settle-rearm\n...\n 1307|d349829629b0c01fb39aaa65a109b5ac5066a7ea d349829629b0c01fb39aaa65a109b5ac5066a7ea Reavo End <decidel@gmail.com> 1783508366 -0700\tcheckout: moving from main to release/0.29.1\n*1308|d349829629b0c01fb39aaa65a109b5ac5066a7ea 6d97dc2b09c4320dacb164c6a0a1a25c67a71f33 Reavo End <decidel@gmail.com> 1783508565 -0700\tcommit: release: v0.29.1 — inject settle-gate re-arm patch (bump + changelog)\n 1309|6d97dc2b09c4320dacb164c6a0a1a25c67a71f33 187d14bd05ba3ed018bd8404d02bc20ee5e5820a Reavo End <decidel@gmail.com> 1783531593 -0700\tcheckout: moving from release/0.29.1 to triage/musl-tier\n 1310|187d14bd05ba3ed018bd8404d02bc20ee5e5820a f1776840c78552ebfc83507c847362689bc68f71 Reavo End <decidel@gmail.com> 1783531610 -0700\tcommit: triage(musl-tier): queue first-class static-musl platform milestone\n 1311|f1776840c78552ebfc83507c847362689bc68f71 f1776840c78552ebfc83507c847362689bc68f71 Reavo End <decidel@gmail.com> 1783548794 -0700\treset: moving to HEAD\n...\n 1329|ff7fe9947e38d87b400e981743568d404036a449 7d776ead6c0f54208dba5b890c84cea3e734013e Reavo End <decidel@gmail.com> 1783561031 -0700\tcheckout: moving from main to feat/musl-w3-release-artifact\n*1330|7d776ead6c0f54208dba5b890c84cea3e734013e ae7b1f62578eead53b072be0daef205b4145bda8 Reavo End <decidel@gmail.com> 1783561178 -0700\tcommit: release: v0.30.0 — static-musl Linux artifact (MUSL-TIER, counter 51)\n 1331|ae7b1f62578eead53b072be0daef205b4145bda8 ae7b1f62578eead53b072be0daef205b4145bda8 Reavo End <decidel@gmail.com> 1783561330 -0700\treset: moving to HEAD\n 1332|ae7b1f62578eead53b072be0daef205b4145bda8 ff7fe9947e38d87b400e981743568d404036a449 Reavo End <decidel@gmail.com> 1783561330 -0700\tcheckout: moving from feat/musl-w3-release-artifact to main\n 1333|ff7fe9947e38d87b400e981743568d404036a449 c85e2a85ffb7e408d7f22a5c5fe952dc58a023c1 Reavo End <decidel@gmail.com> 1783561384 -0700\tcommit: triage(pump-truth): scope peer-pump offline-peer resilience — queue REQ-PUMP-DIAL-FASTFAIL + REQ-PUMP-PEER-ISOLATION\n...\n 1404|a6ff50193646ddf8c68deee300ce535565297840 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740809 -0700\tcheckout: moving from main to release/v0.31.0\n*1405|a6ff50193646ddf8c68deee300ce535565297840 97a0e4f7d14c8a260e600c455e3286579cb0f85e Reavo End <decidel@gmail.com> 1783741119 -0700\tcommit: release: v0.31.0 — messaging delivery-integrity + endpoint identity (counter 58)\n 1406|97a0e4f7d14c8a260e600c455e3286579cb0f85e a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783743756 -0700\tcheckout: moving from release/v0.31.0 to main\n 1407|a6ff50193646ddf8c68deee300ce535565297840 c9586d1734ef2bed887d5725265940cf567228ba Reavo End <decidel@gmail.com> 1783743757 -0700\tpull --ff-only: Fast-forward\n 1408|c9586d1734ef2bed887d5725265940cf567228ba 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784083738 -0700\tcommit: triage(the-forkening): private fork + gh distribution + local docs milestone — ADR-0036 (supersedes ADR-0014), 9 REQs registry-first, CONTEXT amendments\n...\n 1428|e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119502 -0700\tcheckout: moving from main to release/v0.32.0\n*1429|e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 6f9df02d3ef85a8bb1077a74690880dacff1e404 Reavo End <decidel@gmail.com> 1784120066 -0700\tcommit: release: v0.32.0 — private gh channel, node-local docs, one-command update\n 1430|6f9df02d3ef85a8bb1077a74690880dacff1e404 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784122293 -0700\tcheckout: moving from release/v0.32.0 to main\n 1431|e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 ca6386e97034f5a29ee97a6691af9d66fb3d1665 Reavo End <decidel@gmail.com> 1784122295 -0700\tpull origin main: Fast-forward\n 1432|ca6386e97034f5a29ee97a6691af9d66fb3d1665 e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784162001 -0700\tcommit: triage(project-index): milestone JIT — ADR-0037 + 5 REQs registry-first\n...\n 1452|648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700\tcheckout: moving from main to release/v0.33.0\n*1453|34ae823b2ada1c5098b195d6e5f70422f0d7098d 42d06a87401a62d2a8a6a81ccff2532d43ca6ede Reavo End <decidel@gmail.com> 1784192217 -0700\tcommit: release: v0.33.0 (counter 60) — PROJECT-INDEX milestone\n 1454|42d06a87401a62d2a8a6a81ccff2532d43ca6ede 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tcheckout: moving from release/v0.33.0 to main\n*1455|21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n 1456|3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197583 -0700\tcheckout: moving from main to main\n\n## refs/heads/\n### counter39-field-bugs\n 6|043a6d165d365f6a795934edada1ec2d502c56be f4b43c2dca4cc7daa7f4252db53dc113cf516b35 Reavo End <decidel@gmail.com> 1782958383 -0700\tcommit (amend): fix(info): serialize all info.json writers under the per-perch lock (lost-update)\n*7|f4b43c2dca4cc7daa7f4252db53dc113cf516b35 827697ba118bb7ad69265c6c263b9053ab382ccc Reavo End <decidel@gmail.com> 1782959744 -0700\tcommit: release: v0.19.1 — counter-39 field-bug milestone\n### dev-freeform\n 343|d24886a110814925c853dc0b12fd16e010a8d92a bc3c1f2e4cce5e5de55bd75602647d3cab84d428 Reavo End <decidel@gmail.com> 1780973376 -0700\tcommit: docs(changelog): daemon-lifecycle UX + concurrent --nodes probes (Unreleased)\n*344|bc3c1f2e4cce5e5de55bd75602647d3cab84d428 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974008 -0700\tcommit: release: v0.3.0\n 345|35d1672fdf999a4fc6e58fcd04ff5c953452beb0 7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f Reavo End <decidel@gmail.com> 1780974715 -0700\tcommit: fix(daemon): scope service routing to default home + refuse `run` only when it would vanish\n 346|7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f a81cf31f7d4ee6c2cf023b9a11d7781a185c81f8 Reavo End <decidel@gmail.com> 1780976867 -0700\tcommit: docs(changelog): complete the v0.3.0 notes — full-mesh cutover + subnet revoke\n 347|a81cf31f7d4ee6c2cf023b9a11d7781a185c81f8 9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a Reavo End <decidel@gmail.com> 1780978106 -0700\tcommit: docs(deferred): origin-source update bootstrap (`spt update fetch` from GitHub)\n...\n 349|c44e62209cc5a4c6c6ab9d6cfb247728e1974d9b 63fbbee31235a5107d2ccdb364efd77d16af2e6f Reavo End <decidel@gmail.com> 1780980823 -0700\tcommit: feat(update): `spt update fetch` — origin-source bootstrap from GitHub (REQ-UPD-7)\n*350|63fbbee31235a5107d2ccdb364efd77d16af2e6f 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780981240 -0700\tcommit: release: v0.3.1\n 351|516465c1cbf959ed89c77464b38e9c4ed5142ad9 c5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f Reavo End <decidel@gmail.com> 1780982347 -0700\tcommit: fix(proto): CR-linesafe EVENT codec + send/ring stdin trim (REQ-HAZARD-ENVELOPE-CR-LINESAFE)\n 352|c5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f 4e774a391077a9bdce29906aaed6de804da5de1b Reavo End <decidel@gmail.com> 1780997108 -0700\tcommit: docs(plan): V032-PLAN.md — platform-safe update fetch + apply guard (v0.3.1 corruption fix)\n 353|4e774a391077a9bdce29906aaed6de804da5de1b a65b508bc66eca351480ae0ea2d875369fb07629 Reavo End <decidel@gmail.com> 1780998624 -0700\tcommit: feat(update): platform-safe `spt update fetch` + apply platform-guard (REQ-UPD-8)\n*354|a65b508bc66eca351480ae0ea2d875369fb07629 cabdc614fc0416e3a57de0a21b229db485089320 Reavo End <decidel@gmail.com> 1780998629 -0700\tcommit: release: prep v0.3.2 (version bump + CHANGELOG)\n 355|cabdc614fc0416e3a57de0a21b229db485089320 0c954353b256f46e45a600be7a7fb1f3768e9d30 Reavo End <decidel@gmail.com> 1781055987 -0700\tbranch: Reset to 0c95435\n 356|0c954353b256f46e45a600be7a7fb1f3768e9d30 c5887db27f6c8576decec071c073e76c5144ba66 Reavo End <decidel@gmail.com> 1782020232 -0700\tpull: fast-forward\n### f023-wan-idle-delivery\n 18|9731126874812af3ea51ef792347626687418ec8 c65441caae59e200363ca86dd9fba506eb0b5bcb Reavo End <decidel@gmail.com> 1782987421 -0700\tcommit: feat(f024): cli-origin stamp — never a blank sender\n*19|c65441caae59e200363ca86dd9fba506eb0b5bcb 8a4e4e54da442c17800cd6ba7f5b8814454b4b75 Reavo End <decidel@gmail.com> 1782988548 -0700\tcommit: release: v0.20.0 — cross-node WAN-idle delivery + fixes\n### f025-listredesign\n 16|04a3622c0880501a8f4861b048052d0581593a28 b2b1f8639dd13409b1e9902a4740aef9fc356b2e Reavo End <decidel@gmail.com> 1783041555 -0700\tcommit: docs(changelog): v0.21.0 — endpoint-list redesign + update-apply honesty\n*17|b2b1f8639dd13409b1e9902a4740aef9fc356b2e 65261ad9f495896272c25fc0bcd6d7417e9aed32 Reavo End <decidel@gmail.com> 1783041732 -0700\tcommit: release: v0.21.0 — endpoint-list redesign + update-apply honesty\n 18|65261ad9f495896272c25fc0bcd6d7417e9aed32 5694d686a23d57a763e12d66d114ac958599ad23 Reavo End <decidel@gmail.com> 1783041912 -0700\tcommit: docs(changelog): add concurrent-store-init Fixed bullet (doyle gate)\n### f026-pickertruth\n 16|1c0a02ca3f25b68bfd3fdaececaa473df43b1c1f a390490ca49ee65b91744a2c5a2286f02abb234b Reavo End <decidel@gmail.com> 1783058809 -0700\tcommit: feat(f026): W4 #6 — resume view keeps Confirm top panel\n*17|a390490ca49ee65b91744a2c5a2286f02abb234b 7e29ffcc36ba0bfc3b98ee6dacccf2a0c5a25668 Reavo End <decidel@gmail.com> 1783065544 -0700\tcommit: release: v0.22.0 — picker/presence truth (F-026)\n 18|7e29ffcc36ba0bfc3b98ee6dacccf2a0c5a25668 ffca259e610631a732c264072f351ce27c98ea07 Reavo End <decidel@gmail.com> 1783066083 -0700\tcommit: fix(f026): SI-1 test — cfg-gate resolve_endpoint_drop_dir_cases paths (Linux abs-path semantics)\n### field-bug-milestone\n 17|160d8f36eaacda158a62836869d1b0c923e2ea54 b67d23d3a0101662e8e5c3682070851e82dbf42b Reavo End <decidel@gmail.com> 1782899886 -0700\tcommit (amend): test(attach): #4×#6 intersection — cross-node cold attach to an alt-screen session gets the clean repaint\n*18|b67d23d3a0101662e8e5c3682070851e82dbf42b d819a522816b569ada0165e9481241089db573fc Reavo End <decidel@gmail.com> 1782901046 -0700\tcommit: release: v0.19.0 — counter-38 field-bug milestone\n 19|d819a522816b569ada0165e9481241089db573fc a5129cdcb267344731d77852a5c4ac731c9d5251 Reavo End <decidel@gmail.com> 1782905044 -0700\tcommit: test(wedge): env-gate the paste-wedge backpressure REPRODUCTION assert (kitsubito fast-drain)\n### lifecycle-truth\n 46|fbf5d359186bfcec17fa86f2814ceddcc5ad2652 53a04702912c49a7cfa50bf547526125202d5956 Reavo End <decidel@gmail.com> 1783473974 -0700\tcommit: docs(lifecycle-truth): W6 GATE VERDICT — PASS, milestone COMPLETE (all 6 waves gated)\n*47|53a04702912c49a7cfa50bf547526125202d5956 a74e5269e0337504363a77744b65e0765537db69 Reavo End <decidel@gmail.com> 1783474761 -0700\tcommit: release: v0.29.0 — LIFECYCLE-TRUTH (bump + changelog)\n 48|a74e5269e0337504363a77744b65e0765537db69 71907304fffca9ab8e569b72500e281c16c523d0 Reavo End <decidel@gmail.com> 1783476169 -0700\tcommit: fix(ci): n1-gate observation + traceability parse — release v0.29.0 gate-hold\n 49|71907304fffca9ab8e569b72500e281c16c523d0 a907937794efd479d6aaabc6fe6f23ca2a607fe0 Reavo End <decidel@gmail.com> 1783481698 -0700\tcommit: docs(changelog): correct v0.29.0 notes per operator field review\n### main\n 92|e08cea0dd604d378c77233876ff9f9d895d6ff7e 30d2a92a0daa1631af566f3196bf3e07e9a152ee Reavo End <decidel@gmail.com> 1781520755 -0700\tpull --ff-only origin main: Fast-forward\n*93|30d2a92a0daa1631af566f3196bf3e07e9a152ee cb0560052c7392d386d9e68db359bbbf3138dcfe Reavo End <decidel@gmail.com> 1781520973 -0700\tcommit: release: v0.7.1 — <EVENT> envelope everywhere + confident update-apply message\n 94|cb0560052c7392d386d9e68db359bbbf3138dcfe 1fc700baa4fd92c86c43744469cbd074dc4c23fa Reavo End <decidel@gmail.com> 1781529085 -0700\tpull --ff-only origin main: Fast-forward\n 95|1fc700baa4fd92c86c43744469cbd074dc4c23fa b5f61bab9cf4458d9ef71fcf6dedf3e3f02216cf Reavo End <decidel@gmail.com> 1781583167 -0700\tpull --ff-only: Fast-forward\n 96|b5f61bab9cf4458d9ef71fcf6dedf3e3f02216cf 44ed949ed8b890ed7002a76d4a8454489b6ffc64 Reavo End <decidel@gmail.com> 1781583592 -0700\tcommit: docs: add adapter patterns & pitfalls field guide\n...\n 174|648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tbranch: Reset to origin/main\n*175|21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n 176|3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n 177|01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\n 178|b7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700\tmerge origin/main: Fast-forward\n### v0.12.1-lifecycle\n 16|a461dc47963b4790d839eec123a72f24b11c17fb 54f167ca81fb9532fdfb29f9305e279e3d0c5f91 Reavo End <decidel@gmail.com> 1781843853 -0700\tcommit: v0.12.1 docs: regen CLI reference after help-md merge (help strips MD markers)\n*17|54f167ca81fb9532fdfb29f9305e279e3d0c5f91 6473fe7485097ed899e77bfa4f71e9a2707cd554 Reavo End <decidel@gmail.com> 1781846220 -0700\tcommit: release: v0.12.1 — real-harness lifecycle fixes + CLI polish\n 18|6473fe7485097ed899e77bfa4f71e9a2707cd554 185eaeeb77026473a12d7b74432c81a53a0ee43d Reavo End <decidel@gmail.com> 1781846902 -0700\tcommit: fix(ci): test-only — Linux dead-trait gate + breakaway test skips a runner-confounded escape\n 19|185eaeeb77026473a12d7b74432c81a53a0ee43d 86f20ac831033a20bd5e096b1c318a75249179f3 Reavo End <decidel@gmail.com> 1781848891 -0700\tcommit: fix(ci): job_escape_e2e reaps the WHOLE WMI daemon tree (broker + brain)\n\n### feat/\n#### musl-w3-release-artifact\n 4|4138ddb2a952773885cdef097bb55b762caa6d79 7d776ead6c0f54208dba5b890c84cea3e734013e Reavo End <decidel@gmail.com> 1783558621 -0700\tbranch: Reset to origin/feat/musl-w3-release-artifact\n*5|7d776ead6c0f54208dba5b890c84cea3e734013e ae7b1f62578eead53b072be0daef205b4145bda8 Reavo End <decidel@gmail.com> 1783561178 -0700\tcommit: release: v0.30.0 — static-musl Linux artifact (MUSL-TIER, counter 51)\n 6|ae7b1f62578eead53b072be0daef205b4145bda8 8d5a137577225ea08e638f08503ac7a9ec647637 Reavo End <decidel@gmail.com> 1783562557 -0700\tcommit (merge): Merge origin/main into release branch — union traceable-reqs (musl W3 ACTIVATED + PUMP-TRUTH W1/W2 queued)\n\n### release/\n#### 0.29.1\n 1|0000000000000000000000000000000000000000 d349829629b0c01fb39aaa65a109b5ac5066a7ea Reavo End <decidel@gmail.com> 1783508366 -0700\tbranch: Created from HEAD\n*2|d349829629b0c01fb39aaa65a109b5ac5066a7ea 6d97dc2b09c4320dacb164c6a0a1a25c67a71f33 Reavo End <decidel@gmail.com> 1783508565 -0700\tcommit: release: v0.29.1 — inject settle-gate re-arm patch (bump + changelog)\n#### v0.30.4\n 1|0000000000000000000000000000000000000000 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633378 -0700\tbranch: Created from HEAD\n*2|7782bdadc72b6798261d35423a3b983092555983 6bf4ee442b8b47889bcf684b7b0d4686bffcd99b Reavo End <decidel@gmail.com> 1783633423 -0700\tcommit: release: v0.30.4 — update-wedge round-2: resumed sessions re-attach as observers (counter 55)\n#### v0.32.0\n 1|0000000000000000000000000000000000000000 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119502 -0700\tbranch: Created from HEAD\n*2|e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 6f9df02d3ef85a8bb1077a74690880dacff1e404 Reavo End <decidel@gmail.com> 1784120066 -0700\tcommit: release: v0.32.0 — private gh channel, node-local docs, one-command update\n#### v0.33.0\n 1|0000000000000000000000000000000000000000 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700\tbranch: Created from 34ae823\n*2|34ae823b2ada1c5098b195d6e5f70422f0d7098d 42d06a87401a62d2a8a6a81ccff2532d43ca6ede Reavo End <decidel@gmail.com> 1784192217 -0700\tcommit: release: v0.33.0 (counter 60) — PROJECT-INDEX milestone\n#### v0.34.0\n 1|0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tbranch: Created from 01d2d18\n*2|01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n#### v0.35.0\n 1|0000000000000000000000000000000000000000 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784247046 -0700\tbranch: Created from 7f5ead8\n*2|7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 83f90e7e622a0693ae8e6f929c5129352f08b818 Reavo End <decidel@gmail.com> 1784247250 -0700\tcommit: release: v0.35.0 (counter 62) — MESH-RECOVERY W1"}],"details":{"scopePath":".git/logs/refs, .git/logs/HEAD","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":39,"fileCount":17,"files":[".git/logs/refs/heads/counter39-field-bugs",".git/logs/refs/heads/dev-freeform",".git/logs/refs/heads/f023-wan-idle-delivery",".git/logs/refs/heads/f025-listredesign",".git/logs/refs/heads/f026-pickertruth",".git/logs/refs/heads/feat/musl-w3-release-artifact",".git/logs/refs/heads/field-bug-milestone",".git/logs/refs/heads/lifecycle-truth",".git/logs/refs/heads/main",".git/logs/refs/heads/release/0.29.1",".git/logs/refs/heads/release/v0.30.4",".git/logs/refs/heads/release/v0.32.0",".git/logs/refs/heads/release/v0.33.0",".git/logs/refs/heads/release/v0.34.0",".git/logs/refs/heads/release/v0.35.0",".git/logs/refs/heads/v0.12.1-lifecycle",".git/logs/HEAD"],"fileMatches":[{"path":".git/logs/refs/heads/counter39-field-bugs","count":1},{"path":".git/logs/refs/heads/dev-freeform","count":3},{"path":".git/logs/refs/heads/f023-wan-idle-delivery","count":1},{"path":".git/logs/refs/heads/f025-listredesign","count":1},{"path":".git/logs/refs/heads/f026-pickertruth","count":1},{"path":".git/logs/refs/heads/feat/musl-w3-release-artifact","count":1},{"path":".git/logs/refs/heads/field-bug-milestone","count":1},{"path":".git/logs/refs/heads/lifecycle-truth","count":1},{"path":".git/logs/refs/heads/main","count":2},{"path":".git/logs/refs/heads/release/0.29.1","count":1},{"path":".git/logs/refs/heads/release/v0.30.4","count":1},{"path":".git/logs/refs/heads/release/v0.32.0","count":1},{"path":".git/logs/refs/heads/release/v0.33.0","count":1},{"path":".git/logs/refs/heads/release/v0.34.0","count":1},{"path":".git/logs/refs/heads/release/v0.35.0","count":1},{"path":".git/logs/refs/heads/v0.12.1-lifecycle","count":1},{"path":".git/logs/HEAD","count":20}],"truncated":true,"perFileLimitReached":20,"displayContent":"# .git/logs/\n## HEAD\n  361│d24886a110814925c853dc0b12fd16e010a8d92a bc3c1f2e4cce5e5de55bd75602647d3cab84d428 Reavo End <decidel@gmail.com> 1780973376 -0700\tcommit: docs(changelog): daemon-lifecycle UX + concurrent --nodes probes (Unreleased)\n *362│bc3c1f2e4cce5e5de55bd75602647d3cab84d428 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974008 -0700\tcommit: release: v0.3.0\n  363│35d1672fdf999a4fc6e58fcd04ff5c953452beb0 4fa29498d2552f277e918219f74d9c75183a6921 Reavo End <decidel@gmail.com> 1780974018 -0700\tcheckout: moving from dev-freeform to main\n  364│4fa29498d2552f277e918219f74d9c75183a6921 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974019 -0700\tmerge dev-freeform: Fast-forward\n  365│35d1672fdf999a4fc6e58fcd04ff5c953452beb0 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974701 -0700\tcheckout: moving from main to dev-freeform\n     │...\n  379│c44e62209cc5a4c6c6ab9d6cfb247728e1974d9b 63fbbee31235a5107d2ccdb364efd77d16af2e6f Reavo End <decidel@gmail.com> 1780980823 -0700\tcommit: feat(update): `spt update fetch` — origin-source bootstrap from GitHub (REQ-UPD-7)\n *380│63fbbee31235a5107d2ccdb364efd77d16af2e6f 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780981240 -0700\tcommit: release: v0.3.1\n  381│516465c1cbf959ed89c77464b38e9c4ed5142ad9 9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a Reavo End <decidel@gmail.com> 1780981240 -0700\tcheckout: moving from dev-freeform to main\n  382│9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780981240 -0700\tmerge dev-freeform: Fast-forward\n  383│516465c1cbf959ed89c77464b38e9c4ed5142ad9 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780982347 -0700\tcheckout: moving from main to dev-freeform\n     │...\n  392│4e774a391077a9bdce29906aaed6de804da5de1b a65b508bc66eca351480ae0ea2d875369fb07629 Reavo End <decidel@gmail.com> 1780998624 -0700\tcommit: feat(update): platform-safe `spt update fetch` + apply platform-guard (REQ-UPD-8)\n *393│a65b508bc66eca351480ae0ea2d875369fb07629 cabdc614fc0416e3a57de0a21b229db485089320 Reavo End <decidel@gmail.com> 1780998629 -0700\tcommit: release: prep v0.3.2 (version bump + CHANGELOG)\n  394│cabdc614fc0416e3a57de0a21b229db485089320 4e774a391077a9bdce29906aaed6de804da5de1b Reavo End <decidel@gmail.com> 1780999401 -0700\tcheckout: moving from dev-freeform to main\n  395│4e774a391077a9bdce29906aaed6de804da5de1b cabdc614fc0416e3a57de0a21b229db485089320 Reavo End <decidel@gmail.com> 1780999401 -0700\tmerge dev-freeform: Fast-forward\n  396│cabdc614fc0416e3a57de0a21b229db485089320 44497f466c89b104de2a10816c30f5b68c8fd28a Reavo End <decidel@gmail.com> 1781052390 -0700\tcommit: docs(restoration): broker/brain split regression — design + ADR-0018 + hazards + REQ mints\n     │...\n  481│be626bf83329bfb90fef3f2fee15aab87199ce13 f69e0cd8e11c75f18eef74d4c939a5c21ebf1b96 Reavo End <decidel@gmail.com> 1781172899 -0700\tcommit: test(daemon): in-place-rename respawn int E2E (REQ-HAZARD-BRAIN-RESPAWN-PATH int)\n *482│f69e0cd8e11c75f18eef74d4c939a5c21ebf1b96 9fad2c1ade9fbb289216de0590fe3629abc3af2b Reavo End <decidel@gmail.com> 1781173319 -0700\tcommit: release: v0.4.2 — bump + changelog (Linux brain-respawn-path fix)\n  483│9fad2c1ade9fbb289216de0590fe3629abc3af2b 038257bb9c4a405244a6724beaf2ececb30e7111 Reavo End <decidel@gmail.com> 1781173990 -0700\tcheckout: moving from fix/v0.4.2-brain-respawn-path to main\n  484│038257bb9c4a405244a6724beaf2ececb30e7111 725aebf647f8ebe4415fcfd2274488b41cc561f0 Reavo End <decidel@gmail.com> 1781173992 -0700\tpull --ff-only origin main: Fast-forward\n  485│725aebf647f8ebe4415fcfd2274488b41cc561f0 725aebf647f8ebe4415fcfd2274488b41cc561f0 Reavo End <decidel@gmail.com> 1781174801 -0700\tcheckout: moving from main to main\n     │...\n  594│e08cea0dd604d378c77233876ff9f9d895d6ff7e 30d2a92a0daa1631af566f3196bf3e07e9a152ee Reavo End <decidel@gmail.com> 1781520755 -0700\tpull --ff-only origin main: Fast-forward\n *595│30d2a92a0daa1631af566f3196bf3e07e9a152ee cb0560052c7392d386d9e68db359bbbf3138dcfe Reavo End <decidel@gmail.com> 1781520973 -0700\tcommit: release: v0.7.1 — <EVENT> envelope everywhere + confident update-apply message\n  596│cb0560052c7392d386d9e68db359bbbf3138dcfe cb0560052c7392d386d9e68db359bbbf3138dcfe Reavo End <decidel@gmail.com> 1781524142 -0700\tcheckout: moving from main to m11-shell-substrate\n  597│cb0560052c7392d386d9e68db359bbbf3138dcfe 5251d161482051a9a33fdc2360b8abc5027dc42f Reavo End <decidel@gmail.com> 1781524297 -0700\tcommit: M11-T0: shell-ownership audit + register substrate-extension reqs\n  598│5251d161482051a9a33fdc2360b8abc5027dc42f 5251d161482051a9a33fdc2360b8abc5027dc42f Reavo End <decidel@gmail.com> 1781524434 -0700\tcheckout: moving from m11-shell-substrate to docs/bind-intrinsic-auth-trust-model\n     │...\n  714│a461dc47963b4790d839eec123a72f24b11c17fb 54f167ca81fb9532fdfb29f9305e279e3d0c5f91 Reavo End <decidel@gmail.com> 1781843853 -0700\tcommit: v0.12.1 docs: regen CLI reference after help-md merge (help strips MD markers)\n *715│54f167ca81fb9532fdfb29f9305e279e3d0c5f91 6473fe7485097ed899e77bfa4f71e9a2707cd554 Reavo End <decidel@gmail.com> 1781846220 -0700\tcommit: release: v0.12.1 — real-harness lifecycle fixes + CLI polish\n  716│6473fe7485097ed899e77bfa4f71e9a2707cd554 185eaeeb77026473a12d7b74432c81a53a0ee43d Reavo End <decidel@gmail.com> 1781846902 -0700\tcommit: fix(ci): test-only — Linux dead-trait gate + breakaway test skips a runner-confounded escape\n  717│185eaeeb77026473a12d7b74432c81a53a0ee43d 86f20ac831033a20bd5e096b1c318a75249179f3 Reavo End <decidel@gmail.com> 1781848891 -0700\tcommit: fix(ci): job_escape_e2e reaps the WHOLE WMI daemon tree (broker + brain)\n  718│86f20ac831033a20bd5e096b1c318a75249179f3 86f20ac831033a20bd5e096b1c318a75249179f3 Reavo End <decidel@gmail.com> 1781851530 -0700\tcheckout: moving from v0.12.1-lifecycle to v0.12.2-cli-fixes\n     │...\n  972│160d8f36eaacda158a62836869d1b0c923e2ea54 b67d23d3a0101662e8e5c3682070851e82dbf42b Reavo End <decidel@gmail.com> 1782899886 -0700\tcommit (amend): test(attach): #4×#6 intersection — cross-node cold attach to an alt-screen session gets the clean repaint\n *973│b67d23d3a0101662e8e5c3682070851e82dbf42b d819a522816b569ada0165e9481241089db573fc Reavo End <decidel@gmail.com> 1782901046 -0700\tcommit: release: v0.19.0 — counter-38 field-bug milestone\n  974│d819a522816b569ada0165e9481241089db573fc a5129cdcb267344731d77852a5c4ac731c9d5251 Reavo End <decidel@gmail.com> 1782905044 -0700\tcommit: test(wedge): env-gate the paste-wedge backpressure REPRODUCTION assert (kitsubito fast-drain)\n  975│a5129cdcb267344731d77852a5c4ac731c9d5251 a5129cdcb267344731d77852a5c4ac731c9d5251 Reavo End <decidel@gmail.com> 1782941879 -0700\tcheckout: moving from field-bug-milestone to docs/digest-fetcher-published-prose\n  976│a5129cdcb267344731d77852a5c4ac731c9d5251 296a0a97f72ad93631f655c92438fca1301fbdbd Reavo End <decidel@gmail.com> 1782941879 -0700\tcommit: docs(manifest): document [digest] fetcher strategy on the published surface\n     │...\n  985│043a6d165d365f6a795934edada1ec2d502c56be f4b43c2dca4cc7daa7f4252db53dc113cf516b35 Reavo End <decidel@gmail.com> 1782958383 -0700\tcommit (amend): fix(info): serialize all info.json writers under the per-perch lock (lost-update)\n *986│f4b43c2dca4cc7daa7f4252db53dc113cf516b35 827697ba118bb7ad69265c6c263b9053ab382ccc Reavo End <decidel@gmail.com> 1782959744 -0700\tcommit: release: v0.19.1 — counter-39 field-bug milestone\n  987│827697ba118bb7ad69265c6c263b9053ab382ccc fc92ac2826d1aba81175306da7f8bbc45f2e431b Reavo End <decidel@gmail.com> 1782974143 -0700\tcheckout: moving from counter39-field-bugs to endpoint-list-redesign\n  988│fc92ac2826d1aba81175306da7f8bbc45f2e431b 1fe3c51213df9c20f40be94f8213863ccaf03bac Reavo End <decidel@gmail.com> 1782974218 -0700\tcommit: docs(list-redesign): REQ stubs + glossary ruling for node-grouped endpoint list\n  989│1fe3c51213df9c20f40be94f8213863ccaf03bac 1fe3c51213df9c20f40be94f8213863ccaf03bac Reavo End <decidel@gmail.com> 1782974407 -0700\tcheckout: moving from endpoint-list-redesign to endpoint-list-redesign\n     │...\n 1013│9731126874812af3ea51ef792347626687418ec8 c65441caae59e200363ca86dd9fba506eb0b5bcb Reavo End <decidel@gmail.com> 1782987421 -0700\tcommit: feat(f024): cli-origin stamp — never a blank sender\n*1014│c65441caae59e200363ca86dd9fba506eb0b5bcb 8a4e4e54da442c17800cd6ba7f5b8814454b4b75 Reavo End <decidel@gmail.com> 1782988548 -0700\tcommit: release: v0.20.0 — cross-node WAN-idle delivery + fixes\n 1015│8a4e4e54da442c17800cd6ba7f5b8814454b4b75 3043f4e5f4c6d55301f30fb48afc2340f82ae77d Reavo End <decidel@gmail.com> 1783033981 -0700\tcheckout: moving from f023-wan-idle-delivery to f025-listredesign\n 1016│3043f4e5f4c6d55301f30fb48afc2340f82ae77d 2f79062b86e7a0e254566dbe6ae440e6c89de8d9 Reavo End <decidel@gmail.com> 1783034129 -0700\tcommit: docs(f025): mint 3 update-apply honesty REQs (F-025, stages [])\n 1017│2f79062b86e7a0e254566dbe6ae440e6c89de8d9 47576deecd83570acf277ea7986cc07004310763 Reavo End <decidel@gmail.com> 1783034853 -0700\tcommit: feat(f025): surface running broker image version — F-025 item 1a\n     │...\n 1031│04a3622c0880501a8f4861b048052d0581593a28 b2b1f8639dd13409b1e9902a4740aef9fc356b2e Reavo End <decidel@gmail.com> 1783041555 -0700\tcommit: docs(changelog): v0.21.0 — endpoint-list redesign + update-apply honesty\n*1032│b2b1f8639dd13409b1e9902a4740aef9fc356b2e 65261ad9f495896272c25fc0bcd6d7417e9aed32 Reavo End <decidel@gmail.com> 1783041732 -0700\tcommit: release: v0.21.0 — endpoint-list redesign + update-apply honesty\n 1033│65261ad9f495896272c25fc0bcd6d7417e9aed32 5694d686a23d57a763e12d66d114ac958599ad23 Reavo End <decidel@gmail.com> 1783041912 -0700\tcommit: docs(changelog): add concurrent-store-init Fixed bullet (doyle gate)\n 1034│5694d686a23d57a763e12d66d114ac958599ad23 e96f7d0a7f9dba585a1c2d3f70e4d3fd7e4b212f Reavo End <decidel@gmail.com> 1783047440 -0700\tcheckout: moving from f025-listredesign to f026-pickertruth\n 1035│e96f7d0a7f9dba585a1c2d3f70e4d3fd7e4b212f 5adc527ecb96868d1a26ed02f312d6efba87a7c7 Reavo End <decidel@gmail.com> 1783047644 -0700\tcommit: chore(f026): seed BUILD-F026-PICKERTRUTH — mint 9 REQs + triage doc\n     │...\n 1050│a390490ca49ee65b91744a2c5a2286f02abb234b a390490ca49ee65b91744a2c5a2286f02abb234b Reavo End <decidel@gmail.com> 1783065398 -0700\tcheckout: moving from f026-pickertruth to f026-pickertruth\n*1051│a390490ca49ee65b91744a2c5a2286f02abb234b 7e29ffcc36ba0bfc3b98ee6dacccf2a0c5a25668 Reavo End <decidel@gmail.com> 1783065544 -0700\tcommit: release: v0.22.0 — picker/presence truth (F-026)\n 1052│7e29ffcc36ba0bfc3b98ee6dacccf2a0c5a25668 ffca259e610631a732c264072f351ce27c98ea07 Reavo End <decidel@gmail.com> 1783066083 -0700\tcommit: fix(f026): SI-1 test — cfg-gate resolve_endpoint_drop_dir_cases paths (Linux abs-path semantics)\n 1053│ffca259e610631a732c264072f351ce27c98ea07 ffca259e610631a732c264072f351ce27c98ea07 Reavo End <decidel@gmail.com> 1783070519 -0700\tcheckout: moving from f026-pickertruth to fix/changelog-0220-wording\n 1054│ffca259e610631a732c264072f351ce27c98ea07 ad3038ab6d73e1378ac4b9290c7db5e4bea1df54 Reavo End <decidel@gmail.com> 1783070519 -0700\tcommit: docs(changelog): correct two v0.22.0 release-note bullets\n     │...\n 1083│5005a757e3b3a697f6f2ea504605286070699298 5005a757e3b3a697f6f2ea504605286070699298 Reavo End <decidel@gmail.com> 1783116723 -0700\tcheckout: moving from f028-runtruth to f028-runtruth\n*1084│5005a757e3b3a697f6f2ea504605286070699298 0833a485c376d1353dc99b60937af3920a568941 Reavo End <decidel@gmail.com> 1783116909 -0700\tcommit: release: v0.23.0 — RUNTRUTH (F-028)\n 1085│0833a485c376d1353dc99b60937af3920a568941 5341b971030a47dd70c741639c50f33598eb2558 Reavo End <decidel@gmail.com> 1783117097 -0700\tcommit: docs(changelog): v0.23.0 — add two user-facing daemon surfaces (doyle gate)\n 1086│5341b971030a47dd70c741639c50f33598eb2558 9bae13e4136d22f4b5d5d6757b6f206390bfba6a Reavo End <decidel@gmail.com> 1783121596 -0700\tcheckout: moving from f028-runtruth to main\n 1087│9bae13e4136d22f4b5d5d6757b6f206390bfba6a 289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 Reavo End <decidel@gmail.com> 1783121597 -0700\treset: moving to origin/main\n     │...\n 1108│e7b514cf968027a85e71662329ab8283d5cb2d24 0999cc2f21097fc5798538be29f797ef536a0234 Reavo End <decidel@gmail.com> 1783144036 -0700\tmerge origin/main: Merge made by the 'ort' strategy.\n*1109│0999cc2f21097fc5798538be29f797ef536a0234 f8428a8c814a808d0ffabc18a65f8e7adebf7f51 Reavo End <decidel@gmail.com> 1783144184 -0700\tcommit: release: v0.24.0 — PICKER-POLISH (F-029)\n 1110│f8428a8c814a808d0ffabc18a65f8e7adebf7f51 289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 Reavo End <decidel@gmail.com> 1783149420 -0700\tcheckout: moving from f028-runtruth to main\n 1111│289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 188fe1a9f50a8bbc3234263bb16662a17b51a94e Reavo End <decidel@gmail.com> 1783149421 -0700\treset: moving to origin/main\n 1112│188fe1a9f50a8bbc3234263bb16662a17b51a94e 6f668c246e0f2deb3066a141b2311750feaa6f4b Reavo End <decidel@gmail.com> 1783158698 -0700\tcommit: docs(f030): PSYCHE-EPHEMERAL design — per-event driver, residency retirement\n     │...\n 1299│fbf5d359186bfcec17fa86f2814ceddcc5ad2652 53a04702912c49a7cfa50bf547526125202d5956 Reavo End <decidel@gmail.com> 1783473974 -0700\tcommit: docs(lifecycle-truth): W6 GATE VERDICT — PASS, milestone COMPLETE (all 6 waves gated)\n*1300│53a04702912c49a7cfa50bf547526125202d5956 a74e5269e0337504363a77744b65e0765537db69 Reavo End <decidel@gmail.com> 1783474761 -0700\tcommit: release: v0.29.0 — LIFECYCLE-TRUTH (bump + changelog)\n 1301│a74e5269e0337504363a77744b65e0765537db69 71907304fffca9ab8e569b72500e281c16c523d0 Reavo End <decidel@gmail.com> 1783476169 -0700\tcommit: fix(ci): n1-gate observation + traceability parse — release v0.29.0 gate-hold\n 1302│71907304fffca9ab8e569b72500e281c16c523d0 a907937794efd479d6aaabc6fe6f23ca2a607fe0 Reavo End <decidel@gmail.com> 1783481698 -0700\tcommit: docs(changelog): correct v0.29.0 notes per operator field review\n 1303│a907937794efd479d6aaabc6fe6f23ca2a607fe0 8aac39b7a20edb84101f05ad9514800592fd81c8 Reavo End <decidel@gmail.com> 1783504443 -0700\tcheckout: moving from lifecycle-truth to fix/inject-settle-rearm\n     │...\n 1307│d349829629b0c01fb39aaa65a109b5ac5066a7ea d349829629b0c01fb39aaa65a109b5ac5066a7ea Reavo End <decidel@gmail.com> 1783508366 -0700\tcheckout: moving from main to release/0.29.1\n*1308│d349829629b0c01fb39aaa65a109b5ac5066a7ea 6d97dc2b09c4320dacb164c6a0a1a25c67a71f33 Reavo End <decidel@gmail.com> 1783508565 -0700\tcommit: release: v0.29.1 — inject settle-gate re-arm patch (bump + changelog)\n 1309│6d97dc2b09c4320dacb164c6a0a1a25c67a71f33 187d14bd05ba3ed018bd8404d02bc20ee5e5820a Reavo End <decidel@gmail.com> 1783531593 -0700\tcheckout: moving from release/0.29.1 to triage/musl-tier\n 1310│187d14bd05ba3ed018bd8404d02bc20ee5e5820a f1776840c78552ebfc83507c847362689bc68f71 Reavo End <decidel@gmail.com> 1783531610 -0700\tcommit: triage(musl-tier): queue first-class static-musl platform milestone\n 1311│f1776840c78552ebfc83507c847362689bc68f71 f1776840c78552ebfc83507c847362689bc68f71 Reavo End <decidel@gmail.com> 1783548794 -0700\treset: moving to HEAD\n     │...\n 1329│ff7fe9947e38d87b400e981743568d404036a449 7d776ead6c0f54208dba5b890c84cea3e734013e Reavo End <decidel@gmail.com> 1783561031 -0700\tcheckout: moving from main to feat/musl-w3-release-artifact\n*1330│7d776ead6c0f54208dba5b890c84cea3e734013e ae7b1f62578eead53b072be0daef205b4145bda8 Reavo End <decidel@gmail.com> 1783561178 -0700\tcommit: release: v0.30.0 — static-musl Linux artifact (MUSL-TIER, counter 51)\n 1331│ae7b1f62578eead53b072be0daef205b4145bda8 ae7b1f62578eead53b072be0daef205b4145bda8 Reavo End <decidel@gmail.com> 1783561330 -0700\treset: moving to HEAD\n 1332│ae7b1f62578eead53b072be0daef205b4145bda8 ff7fe9947e38d87b400e981743568d404036a449 Reavo End <decidel@gmail.com> 1783561330 -0700\tcheckout: moving from feat/musl-w3-release-artifact to main\n 1333│ff7fe9947e38d87b400e981743568d404036a449 c85e2a85ffb7e408d7f22a5c5fe952dc58a023c1 Reavo End <decidel@gmail.com> 1783561384 -0700\tcommit: triage(pump-truth): scope peer-pump offline-peer resilience — queue REQ-PUMP-DIAL-FASTFAIL + REQ-PUMP-PEER-ISOLATION\n     │...\n 1404│a6ff50193646ddf8c68deee300ce535565297840 a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783740809 -0700\tcheckout: moving from main to release/v0.31.0\n*1405│a6ff50193646ddf8c68deee300ce535565297840 97a0e4f7d14c8a260e600c455e3286579cb0f85e Reavo End <decidel@gmail.com> 1783741119 -0700\tcommit: release: v0.31.0 — messaging delivery-integrity + endpoint identity (counter 58)\n 1406│97a0e4f7d14c8a260e600c455e3286579cb0f85e a6ff50193646ddf8c68deee300ce535565297840 Reavo End <decidel@gmail.com> 1783743756 -0700\tcheckout: moving from release/v0.31.0 to main\n 1407│a6ff50193646ddf8c68deee300ce535565297840 c9586d1734ef2bed887d5725265940cf567228ba Reavo End <decidel@gmail.com> 1783743757 -0700\tpull --ff-only: Fast-forward\n 1408│c9586d1734ef2bed887d5725265940cf567228ba 3cb54b4df8a131b99bdd11447ec8fc852657cf6c Reavo End <decidel@gmail.com> 1784083738 -0700\tcommit: triage(the-forkening): private fork + gh distribution + local docs milestone — ADR-0036 (supersedes ADR-0014), 9 REQs registry-first, CONTEXT amendments\n     │...\n 1428│e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119502 -0700\tcheckout: moving from main to release/v0.32.0\n*1429│e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 6f9df02d3ef85a8bb1077a74690880dacff1e404 Reavo End <decidel@gmail.com> 1784120066 -0700\tcommit: release: v0.32.0 — private gh channel, node-local docs, one-command update\n 1430│6f9df02d3ef85a8bb1077a74690880dacff1e404 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784122293 -0700\tcheckout: moving from release/v0.32.0 to main\n 1431│e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 ca6386e97034f5a29ee97a6691af9d66fb3d1665 Reavo End <decidel@gmail.com> 1784122295 -0700\tpull origin main: Fast-forward\n 1432│ca6386e97034f5a29ee97a6691af9d66fb3d1665 e5f053fd130f7eed1e40e927c0bab87d88bb007a Reavo End <decidel@gmail.com> 1784162001 -0700\tcommit: triage(project-index): milestone JIT — ADR-0037 + 5 REQs registry-first\n     │...\n 1452│648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700\tcheckout: moving from main to release/v0.33.0\n*1453│34ae823b2ada1c5098b195d6e5f70422f0d7098d 42d06a87401a62d2a8a6a81ccff2532d43ca6ede Reavo End <decidel@gmail.com> 1784192217 -0700\tcommit: release: v0.33.0 (counter 60) — PROJECT-INDEX milestone\n 1454│42d06a87401a62d2a8a6a81ccff2532d43ca6ede 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tcheckout: moving from release/v0.33.0 to main\n*1455│21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n 1456│3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197583 -0700\tcheckout: moving from main to main\n\n## refs/heads/\n### counter39-field-bugs\n 6│043a6d165d365f6a795934edada1ec2d502c56be f4b43c2dca4cc7daa7f4252db53dc113cf516b35 Reavo End <decidel@gmail.com> 1782958383 -0700\tcommit (amend): fix(info): serialize all info.json writers under the per-perch lock (lost-update)\n*7│f4b43c2dca4cc7daa7f4252db53dc113cf516b35 827697ba118bb7ad69265c6c263b9053ab382ccc Reavo End <decidel@gmail.com> 1782959744 -0700\tcommit: release: v0.19.1 — counter-39 field-bug milestone\n### dev-freeform\n 343│d24886a110814925c853dc0b12fd16e010a8d92a bc3c1f2e4cce5e5de55bd75602647d3cab84d428 Reavo End <decidel@gmail.com> 1780973376 -0700\tcommit: docs(changelog): daemon-lifecycle UX + concurrent --nodes probes (Unreleased)\n*344│bc3c1f2e4cce5e5de55bd75602647d3cab84d428 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974008 -0700\tcommit: release: v0.3.0\n 345│35d1672fdf999a4fc6e58fcd04ff5c953452beb0 7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f Reavo End <decidel@gmail.com> 1780974715 -0700\tcommit: fix(daemon): scope service routing to default home + refuse `run` only when it would vanish\n 346│7f63373d8ac246c2c6fe6c1c90de3e04936e0f1f a81cf31f7d4ee6c2cf023b9a11d7781a185c81f8 Reavo End <decidel@gmail.com> 1780976867 -0700\tcommit: docs(changelog): complete the v0.3.0 notes — full-mesh cutover + subnet revoke\n 347│a81cf31f7d4ee6c2cf023b9a11d7781a185c81f8 9c825cfbdcae8fcf08ddbddf8c37c80997e54c4a Reavo End <decidel@gmail.com> 1780978106 -0700\tcommit: docs(deferred): origin-source update bootstrap (`spt update fetch` from GitHub)\n    │...\n 349│c44e62209cc5a4c6c6ab9d6cfb247728e1974d9b 63fbbee31235a5107d2ccdb364efd77d16af2e6f Reavo End <decidel@gmail.com> 1780980823 -0700\tcommit: feat(update): `spt update fetch` — origin-source bootstrap from GitHub (REQ-UPD-7)\n*350│63fbbee31235a5107d2ccdb364efd77d16af2e6f 516465c1cbf959ed89c77464b38e9c4ed5142ad9 Reavo End <decidel@gmail.com> 1780981240 -0700\tcommit: release: v0.3.1\n 351│516465c1cbf959ed89c77464b38e9c4ed5142ad9 c5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f Reavo End <decidel@gmail.com> 1780982347 -0700\tcommit: fix(proto): CR-linesafe EVENT codec + send/ring stdin trim (REQ-HAZARD-ENVELOPE-CR-LINESAFE)\n 352│c5e3b27dcdfd22b04ee42cfe6198ee46b8f9a39f 4e774a391077a9bdce29906aaed6de804da5de1b Reavo End <decidel@gmail.com> 1780997108 -0700\tcommit: docs(plan): V032-PLAN.md — platform-safe update fetch + apply guard (v0.3.1 corruption fix)\n 353│4e774a391077a9bdce29906aaed6de804da5de1b a65b508bc66eca351480ae0ea2d875369fb07629 Reavo End <decidel@gmail.com> 1780998624 -0700\tcommit: feat(update): platform-safe `spt update fetch` + apply platform-guard (REQ-UPD-8)\n*354│a65b508bc66eca351480ae0ea2d875369fb07629 cabdc614fc0416e3a57de0a21b229db485089320 Reavo End <decidel@gmail.com> 1780998629 -0700\tcommit: release: prep v0.3.2 (version bump + CHANGELOG)\n 355│cabdc614fc0416e3a57de0a21b229db485089320 0c954353b256f46e45a600be7a7fb1f3768e9d30 Reavo End <decidel@gmail.com> 1781055987 -0700\tbranch: Reset to 0c95435\n 356│0c954353b256f46e45a600be7a7fb1f3768e9d30 c5887db27f6c8576decec071c073e76c5144ba66 Reavo End <decidel@gmail.com> 1782020232 -0700\tpull: fast-forward\n### f023-wan-idle-delivery\n 18│9731126874812af3ea51ef792347626687418ec8 c65441caae59e200363ca86dd9fba506eb0b5bcb Reavo End <decidel@gmail.com> 1782987421 -0700\tcommit: feat(f024): cli-origin stamp — never a blank sender\n*19│c65441caae59e200363ca86dd9fba506eb0b5bcb 8a4e4e54da442c17800cd6ba7f5b8814454b4b75 Reavo End <decidel@gmail.com> 1782988548 -0700\tcommit: release: v0.20.0 — cross-node WAN-idle delivery + fixes\n### f025-listredesign\n 16│04a3622c0880501a8f4861b048052d0581593a28 b2b1f8639dd13409b1e9902a4740aef9fc356b2e Reavo End <decidel@gmail.com> 1783041555 -0700\tcommit: docs(changelog): v0.21.0 — endpoint-list redesign + update-apply honesty\n*17│b2b1f8639dd13409b1e9902a4740aef9fc356b2e 65261ad9f495896272c25fc0bcd6d7417e9aed32 Reavo End <decidel@gmail.com> 1783041732 -0700\tcommit: release: v0.21.0 — endpoint-list redesign + update-apply honesty\n 18│65261ad9f495896272c25fc0bcd6d7417e9aed32 5694d686a23d57a763e12d66d114ac958599ad23 Reavo End <decidel@gmail.com> 1783041912 -0700\tcommit: docs(changelog): add concurrent-store-init Fixed bullet (doyle gate)\n### f026-pickertruth\n 16│1c0a02ca3f25b68bfd3fdaececaa473df43b1c1f a390490ca49ee65b91744a2c5a2286f02abb234b Reavo End <decidel@gmail.com> 1783058809 -0700\tcommit: feat(f026): W4 #6 — resume view keeps Confirm top panel\n*17│a390490ca49ee65b91744a2c5a2286f02abb234b 7e29ffcc36ba0bfc3b98ee6dacccf2a0c5a25668 Reavo End <decidel@gmail.com> 1783065544 -0700\tcommit: release: v0.22.0 — picker/presence truth (F-026)\n 18│7e29ffcc36ba0bfc3b98ee6dacccf2a0c5a25668 ffca259e610631a732c264072f351ce27c98ea07 Reavo End <decidel@gmail.com> 1783066083 -0700\tcommit: fix(f026): SI-1 test — cfg-gate resolve_endpoint_drop_dir_cases paths (Linux abs-path semantics)\n### field-bug-milestone\n 17│160d8f36eaacda158a62836869d1b0c923e2ea54 b67d23d3a0101662e8e5c3682070851e82dbf42b Reavo End <decidel@gmail.com> 1782899886 -0700\tcommit (amend): test(attach): #4×#6 intersection — cross-node cold attach to an alt-screen session gets the clean repaint\n*18│b67d23d3a0101662e8e5c3682070851e82dbf42b d819a522816b569ada0165e9481241089db573fc Reavo End <decidel@gmail.com> 1782901046 -0700\tcommit: release: v0.19.0 — counter-38 field-bug milestone\n 19│d819a522816b569ada0165e9481241089db573fc a5129cdcb267344731d77852a5c4ac731c9d5251 Reavo End <decidel@gmail.com> 1782905044 -0700\tcommit: test(wedge): env-gate the paste-wedge backpressure REPRODUCTION assert (kitsubito fast-drain)\n### lifecycle-truth\n 46│fbf5d359186bfcec17fa86f2814ceddcc5ad2652 53a04702912c49a7cfa50bf547526125202d5956 Reavo End <decidel@gmail.com> 1783473974 -0700\tcommit: docs(lifecycle-truth): W6 GATE VERDICT — PASS, milestone COMPLETE (all 6 waves gated)\n*47│53a04702912c49a7cfa50bf547526125202d5956 a74e5269e0337504363a77744b65e0765537db69 Reavo End <decidel@gmail.com> 1783474761 -0700\tcommit: release: v0.29.0 — LIFECYCLE-TRUTH (bump + changelog)\n 48│a74e5269e0337504363a77744b65e0765537db69 71907304fffca9ab8e569b72500e281c16c523d0 Reavo End <decidel@gmail.com> 1783476169 -0700\tcommit: fix(ci): n1-gate observation + traceability parse — release v0.29.0 gate-hold\n 49│71907304fffca9ab8e569b72500e281c16c523d0 a907937794efd479d6aaabc6fe6f23ca2a607fe0 Reavo End <decidel@gmail.com> 1783481698 -0700\tcommit: docs(changelog): correct v0.29.0 notes per operator field review\n### main\n  92│e08cea0dd604d378c77233876ff9f9d895d6ff7e 30d2a92a0daa1631af566f3196bf3e07e9a152ee Reavo End <decidel@gmail.com> 1781520755 -0700\tpull --ff-only origin main: Fast-forward\n *93│30d2a92a0daa1631af566f3196bf3e07e9a152ee cb0560052c7392d386d9e68db359bbbf3138dcfe Reavo End <decidel@gmail.com> 1781520973 -0700\tcommit: release: v0.7.1 — <EVENT> envelope everywhere + confident update-apply message\n  94│cb0560052c7392d386d9e68db359bbbf3138dcfe 1fc700baa4fd92c86c43744469cbd074dc4c23fa Reavo End <decidel@gmail.com> 1781529085 -0700\tpull --ff-only origin main: Fast-forward\n  95│1fc700baa4fd92c86c43744469cbd074dc4c23fa b5f61bab9cf4458d9ef71fcf6dedf3e3f02216cf Reavo End <decidel@gmail.com> 1781583167 -0700\tpull --ff-only: Fast-forward\n  96│b5f61bab9cf4458d9ef71fcf6dedf3e3f02216cf 44ed949ed8b890ed7002a76d4a8454489b6ffc64 Reavo End <decidel@gmail.com> 1781583592 -0700\tcommit: docs: add adapter patterns & pitfalls field guide\n    │...\n 174│648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tbranch: Reset to origin/main\n*175│21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n 176│3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n 177│01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\n 178│b7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700\tmerge origin/main: Fast-forward\n### v0.12.1-lifecycle\n 16│a461dc47963b4790d839eec123a72f24b11c17fb 54f167ca81fb9532fdfb29f9305e279e3d0c5f91 Reavo End <decidel@gmail.com> 1781843853 -0700\tcommit: v0.12.1 docs: regen CLI reference after help-md merge (help strips MD markers)\n*17│54f167ca81fb9532fdfb29f9305e279e3d0c5f91 6473fe7485097ed899e77bfa4f71e9a2707cd554 Reavo End <decidel@gmail.com> 1781846220 -0700\tcommit: release: v0.12.1 — real-harness lifecycle fixes + CLI polish\n 18│6473fe7485097ed899e77bfa4f71e9a2707cd554 185eaeeb77026473a12d7b74432c81a53a0ee43d Reavo End <decidel@gmail.com> 1781846902 -0700\tcommit: fix(ci): test-only — Linux dead-trait gate + breakaway test skips a runner-confounded escape\n 19│185eaeeb77026473a12d7b74432c81a53a0ee43d 86f20ac831033a20bd5e096b1c318a75249179f3 Reavo End <decidel@gmail.com> 1781848891 -0700\tcommit: fix(ci): job_escape_e2e reaps the WHOLE WMI daemon tree (broker + brain)\n\n### feat/\n#### musl-w3-release-artifact\n 4│4138ddb2a952773885cdef097bb55b762caa6d79 7d776ead6c0f54208dba5b890c84cea3e734013e Reavo End <decidel@gmail.com> 1783558621 -0700\tbranch: Reset to origin/feat/musl-w3-release-artifact\n*5│7d776ead6c0f54208dba5b890c84cea3e734013e ae7b1f62578eead53b072be0daef205b4145bda8 Reavo End <decidel@gmail.com> 1783561178 -0700\tcommit: release: v0.30.0 — static-musl Linux artifact (MUSL-TIER, counter 51)\n 6│ae7b1f62578eead53b072be0daef205b4145bda8 8d5a137577225ea08e638f08503ac7a9ec647637 Reavo End <decidel@gmail.com> 1783562557 -0700\tcommit (merge): Merge origin/main into release branch — union traceable-reqs (musl W3 ACTIVATED + PUMP-TRUTH W1/W2 queued)\n\n### release/\n#### 0.29.1\n 1│0000000000000000000000000000000000000000 d349829629b0c01fb39aaa65a109b5ac5066a7ea Reavo End <decidel@gmail.com> 1783508366 -0700\tbranch: Created from HEAD\n*2│d349829629b0c01fb39aaa65a109b5ac5066a7ea 6d97dc2b09c4320dacb164c6a0a1a25c67a71f33 Reavo End <decidel@gmail.com> 1783508565 -0700\tcommit: release: v0.29.1 — inject settle-gate re-arm patch (bump + changelog)\n#### v0.30.4\n 1│0000000000000000000000000000000000000000 7782bdadc72b6798261d35423a3b983092555983 Reavo End <decidel@gmail.com> 1783633378 -0700\tbranch: Created from HEAD\n*2│7782bdadc72b6798261d35423a3b983092555983 6bf4ee442b8b47889bcf684b7b0d4686bffcd99b Reavo End <decidel@gmail.com> 1783633423 -0700\tcommit: release: v0.30.4 — update-wedge round-2: resumed sessions re-attach as observers (counter 55)\n#### v0.32.0\n 1│0000000000000000000000000000000000000000 e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 Reavo End <decidel@gmail.com> 1784119502 -0700\tbranch: Created from HEAD\n*2│e0a8bb00d76315ba8a1d82ba5fb60e4515932ab3 6f9df02d3ef85a8bb1077a74690880dacff1e404 Reavo End <decidel@gmail.com> 1784120066 -0700\tcommit: release: v0.32.0 — private gh channel, node-local docs, one-command update\n#### v0.33.0\n 1│0000000000000000000000000000000000000000 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700\tbranch: Created from 34ae823\n*2│34ae823b2ada1c5098b195d6e5f70422f0d7098d 42d06a87401a62d2a8a6a81ccff2532d43ca6ede Reavo End <decidel@gmail.com> 1784192217 -0700\tcommit: release: v0.33.0 (counter 60) — PROJECT-INDEX milestone\n#### v0.34.0\n 1│0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tbranch: Created from 01d2d18\n*2│01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n#### v0.35.0\n 1│0000000000000000000000000000000000000000 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784247046 -0700\tbranch: Created from 7f5ead8\n*2│7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 83f90e7e622a0693ae8e6f929c5129352f08b818 Reavo End <decidel@gmail.com> 1784247250 -0700\tcommit: release: v0.35.0 (counter 62) — MESH-RECOVERY W1"},"isError":false,"timestamp":1784247370262}}
{"type":"message","id":"6934ca19","parentId":"bf34c2de","timestamp":"2026-07-17T00:16:28.129Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Investigating release commit discrepancies**\n\n**Analyzing broker and brain version compatibility**\n\n**Identifying durable opener capture in v34**"},{"type":"thinking","thinking":""},{"type":"toolCall","id":"call_BUk8V8IStGkHLL21PZfdAssF|fc_099a7567dadef391016a59745c12c0819bac7fed3606807308","name":"read","arguments":{"i":"Reading release history window","path":".git/logs/HEAD","selector":"1448-1470"}},{"type":"toolCall","id":"call_Oz9VXMBZ3DG96XdPhHBilCil|fc_099a7567dadef391016a59745c1350819bb9604c69a533eb35","name":"read","arguments":{"i":"Reading release branch history","path":".git/logs/refs/heads/release/v0.34.0","selector":"raw"}},{"type":"toolCall","id":"call_0M70HXxGeGtEsQxrDqqUwiJ3|fc_099a7567dadef391016a59745c135c819b860e5ff5b5ac5fc1","name":"read","arguments":{"i":"Reading redispatch loop","path":"crates/spt-daemon/src/dispatch.rs","selector":"155-430"}},{"type":"toolCall","id":"call_9e0VDa4NuEHOPgiwgQthJjvV|fc_099a7567dadef391016a59745c1368819ba561b1676efd7561","name":"read","arguments":{"i":"Reading redispatch tests","path":"crates/spt-daemon/tests/redispatch.rs","selector":"200-520"}},{"type":"toolCall","id":"call_sYyOxv1UgqgkMKZy1Hlq1JTH|fc_099a7567dadef391016a59745c1378819b8563469b6fc31ede","name":"read","arguments":{"i":"Reading refresh integration","path":"crates/spt/tests/daemon_refresh_e2e.rs","selector":"180-340"}},{"type":"toolCall","id":"call_hkGjyt1zE28moix9pK6MZCns|fc_099a7567dadef391016a59745c138c819b9a78a47fdfbb7c1a","name":"read","arguments":{"i":"Reading update apply cycle","path":"crates/spt-daemon/src/applyhost.rs","selector":"300-390"}},{"type":"toolCall","id":"call_IsSj9g4e2EjK4G6922d7eKwK|fc_099a7567dadef391016a59745c1390819b9786b9878fdf862d","name":"grep","arguments":{"i":"Finding compatibility delays","pattern":"N-1|older broker|predates|fallback|backoff|Duration::from_secs\\((15|20|30)\\)|BRAIN_WRITE_DEADLINE|CONTROLLER_WRITE_DEADLINE|recv_timeout","path":"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/src/brain.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt/tests/daemon_refresh_e2e.rs; docs/adr/0038-broker-stream-lifecycle-truth.md; traceable-reqs.toml","selector":null,"case":false,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":26499,"output":925,"cacheRead":55808,"cacheWrite":0,"totalTokens":83232,"reasoningTokens":528,"cost":{"input":0.132495,"output":0.02775,"cacheRead":0.027903999999999998,"cacheWrite":0,"total":0.188149}},"stopReason":"toolUse","timestamp":1784247370268,"responseId":"resp_099a7567dadef391016a59744a7614819b8ed0be8313d406ab","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a59744b6a5c819b8c3cf1df75a4e195","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXRXN0eL4f8xEVNZ_bd3eDobikB9ulahf2WSoyOEaSjMK6kF984bev3rGVdOrIfIaoqSoG9-Ge_M39mKIZorsrqGo1Ogt_JFwcwtNppKCuAtK9xzGlpKvHB-yw7pVLYLGk7alTdpEq5SHflG14qG98r020FGFFjSY_5gSYvM_PUeKydxhQpAL7D6Sqm2EYUd3203KJTt0ba_dKrPDqMi_0ZfOPK9e33aEn0JRZud2yWHnqmg_i1oeWjBN8Z3hzpzWYH2B8nmIXqQOL0vAlRAR9Ify2UFKZqndSXHY8BS0ujjk61E7OkkLegmwK0o3VaBLdD8pbIhG2zslwiImoSPdDfirVkIgp1HSQPmJnfQUcr_ZxN-9_THI3CaXUkCT8yt-EEqb1hBd5JU9LDaqCLFYoDkoIguCio8S4wEeYmw5A-ghSxGJ--JlzBi0JPsi2xKETRmHFHGpdaPC9J9RzZosWEqA0dJSQk1iNLI3Nf9M-NlZgPK7J0bn201hU6JntmA41JTitcpw99zwP8nJ68GrA3S-CcNN7gUuFy2U9-3od9Ci30arNF9fQlV92C_0zMPvPoAVGJfLcws_JEP05AdH6apvJrX1TGssEk00o1k_nYrUVu50aobB6dRgyJ4lMC21aEvkjM7zh117FPgh83_l7Sm3SAuam4Xd7Lqz5KL78HwHPlPNYbLH5rBqSJDzafsvQqsCDp5Cc6t-lYjYpWGqM-xDA5sbVwNwo9ERiQEWp5Y1QCisXwJRsMcnV1oxR-fLU7U6kiCS7z8qNAm7qa4V_SNHfsrGcLTZ79hW8sv1BqU_Ayjyzp2clp-OAWg-lKRk6-ZeZ62SNUZZ9Q__xoK9Igm22jKtqXzg8DzicYGAY_lptHQQP3c5Ff7bmqWckzGHpo0nFOIaEskB1Pke6c7f4RZYm9unybcxvRvVLXcBQvcKIoNfAjYmYxriTk9yEsW-cQcTZX2iKSZyT8yjR1TXprPcyDk5MPeW23pfwdQMMLYhLsjFNVcg_K_9AufaSxv__IFV5P9X92p6Xb988U_yK_9ZiSNw2UuOEDozhsCiJ-ItSAWWRbJVdKB6AWWmpL5WfLsV0SqweF9d84gamDMceJ2tTfg5YUjByHrifc8SVwjZs4lyh6NE5G97Keqst61UcTB02LPG_AlA2L6WWmp8cRF2VasLuCoSbdRCQIwQ_q-O9fTmzSAlKdOGCQXasiDLYP4jSJ3o2SqnENy6yOEuyZLF--IbmyJSjCtTfr1BWagx1Stn7WTcRG_nu6qyZl9DhyuPClHZJq7OiUx7w0lmNbPXeBNP-KBEFsFBxQayT17hFrZawkBj03651N-eEOZFiANQvXCI4BfcS1HZEnDnRHvuxwqLqvfTeyNi-D1n8xABBLCNZsOt7I-bwy7c5CvbCi8QH0QEY6nWNqG5Ztcw_kiDPVeRVai0MxOUyqGHqHPYC6jU44vXM0CByzPhH15IrwJGhDcNVQERcKR5S0W3O3eBMVtrv3ZG-OwTM38aeFffcaFbjL0uDKz4botDCt-2pUenkJYqwc0PpuNW4NCryxGGo8woEYH_6EiHfZG_d7I97sqzPu2M4enBRQaCLUH7O-N8oL1KQ0T1W9jzvMQbOj8T6q7doJUhHmhns9HX5RAliVjKfIQ0cfhSCHHlp2nBczpzXA4L9ZRdWSpenNvVzqzPLv5P3BWI1TK7FmvarM1N-uReCZHd_yaohD039jHzlp0x_CLaaDOx5SaLcFEr4MsujB97J2OVdoX0rwdAw1rL0AzGNrLLrIr0kO3xJloDhT2hUMrywrXOHWMN-3OQoYnKAXYYVg6OpcpxV3Mvh-YH25cYWbUTZxoA55KAvbgMgXVLXg-zhRLVdEWVVPV4gidLj-HTxjspGiKNqCwKbL0O1TTb5K0WhbmA_8fV-qjOIFR1dnQn4CGk-0euy-NQmwz80Rcx4yLQnPWCcVpbPTU7xQL3iDOY_sBCi9GBXszeKQjA7JFgnAKkapHu2qVyY05ta2sJkP4BlpTjjyReOYt8Fv699fLkCRyktYa0G_n6KwbGedXLpNeShj7qszrLuoQXk34vrA3XYCkGiJum4nF6u49ikjxl-_NLqLzGrKMvSsmJAa3UL2_Phwx7JWsd3aX9ecCV8dVXMn9IBnwh_nd_bDYmKR99YJwoDxiw22F738B7sztmnIsxbUMQlngI93L-nIVGawAkmpTTOoGU-fJciSS6wwcs2uGLJ-0VqjmQOXtmVNbPHVYw6uNGz3rG69o-kuJvJfJ1XAM7zYZDmszVcvqJtMJ6ile5OxXS7yZ4OF-JwNzTWb184NrYAPiiIphXh_QjAHoMWfikHTUyA1LrbKassYEzEcUQsKED9z2I9c3mA-vSqx117eAmtLMOyQXNYbNN950sumZEOe4-irJJ9x88i1ZNRo0DagNN6N6jYa_Z3Y5ZvpiHsLw7qrSxAg07OAG-oLVeDEA7k7FN-iQggcjThP7XFWbza0PUGyCvWsGfXeZ8tPELnh6-F8uGJWJ8Xh_Nm3_cTMCVXkopbxX05kYcLdBtG0vAFCMvKDIHXBeTWaFfZ3jg_ale4DTd6FYBCtFTm6Awadk4BFJ74sXFMKj1t1jrIt-m7KvhissXzrPYTa9LXIgcwBwBRv93Mw2lP3_TFrpjkVlslFeQPfEcAiN0HPUEwzVUcRRV-5JlH5Np5hVW6VLX7Sr93xFohdbLvmrDiX_BQmL7VmVk7iPHs3i_aBxacg8zvKLVpQPsHf2C05z9XLFk2uoU9gsInrOkSL22BMOcyce_WVaGOQWK1X7PQg3pPUn72m1LXTAsRf5AByA7q3YU7J1nds9xcLlk7v5bJyl1qdLsm1tBsb1ELk1E3EWYCmLzyT7UUVqvMEIiVtkPCoHpEcptA5OgpYfMlZjjl5h42souPKxTwCEOnsRiwGvVZo3B96UbVebhLkFtdp0PKl_1fTyDGZks3KZ0RNX0TAR4q2oU40CRgGNNy_7z5TcY5NO-8hSIdC_dRjEFaLi7fNaTMwsTtThhPiFV-aMh7-PEoR2QZr-1EixHsjLvGMtquD_yKsF-0IGCcSWWuVcVKYxvAvjLiof8k7YTu0lZHLSMeJUxQQ2XVYZ7m7y_buokcwV5SLOoz_3MWl74UpydjkoSVJBfT1AWTbmo_QVNXuNDkKnbXDi8qaH1SneRxUC5dZGES6NH4mf_ZU1DAQt4jchDbIZ6kA4yKGcpWyeYrwr06mgpGlggmBGj6JbPA2KlzjZ5tFHnEqnVv_RLmZ7rrPf355DDSCdhckhIGlIOR56VkUrasccx-v01phJXMIiy5nbIeCByxTKSx5YKv4CgLn6ocsgt8wuJuVZAlmbovS8OiPe9UIaFOGB87bqH6Jm5xMK_dmKHvVjCYwgK9PXVhmjFoqQXEc_YKdLP6yD7LCDPDLRehTbJZh_TfdV_6j4DR2Iy-j1l5Q2SetNYyj5QzbcvuzhV_hCh8VXUPZQypB30mRosc-RFd69ix2bUKitKGYWXEz9vg70ehCrInSlFkPWFNrba08uHLPbSKs3lZ9O_uvrjIM-OoAcJRYjZ_sLkZyz5ZTuUcY8theTYx_HKzbOQNIxmPH9gpMoIzRXPsp38zgjlWwip8jB9ft63Jy5Xpn2IiMHrXtvAD9aHbC8Mrsf6ff2han1lssR5lclqHNiJXTH5LN5VfDbSeQXCr2V7ZIulsdU9B4YAcQTnPEJeb3XZBVNRCDsOpL6pMTttyogKx8NkuY8WcYbRJC_gzv13KVvOao5gKJsCjYns6JQFPyUavR7kzTTOqJUZPKQq-9Oifq4OvuDGlMMt3DBqHJu7BJhakv24bYiHNslXahs2Q2VMjOI17fB1LTCAW0RtOL7zjTrLPfr-FIWDvfxIU4UP-u8eUvIDbgZXvY3Ds3Dcsqr7FtJCOcuNUqd8HbzkiKV-ld_Iby5NDmpzCOQISrguEMe5NuBWpibYIeEIwQ187W1TlALXuGAu44PMQ==","summary":[{"type":"summary_text","text":"**Investigating release commit discrepancies**"},{"type":"summary_text","text":"**Analyzing broker and brain version compatibility**"},{"type":"summary_text","text":"**Identifying durable opener capture in v34**"}]},{"id":"rs_099a7567dadef391016a5974571c00819bb378f8c17caad5b0","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXRcS83XtWVqOBmXi2IAFvNoKf3WJLhFpIikHDhyiUH3FwjRwt-O04dKBArdXDwXErBxOixDoLwYafvPnCg6lM0W4fNnbkTYYtocN1SPgHhUaq1RHMlR7CQsNzP7lh7W0Butx87-eCsWi8UswYVvtsu0oGKOxnjKON71jj9CEQNCa0nYSOKDI_YvgyFlZbkG6yJKe9sEe5O1h_Qb05d_FjHvmbx5rUKoQdsiHABtwnTD6oY-K96Q7CrrOfEvoq5Mpe8Lx8F4YXHwGqJ4Xkr2DbnvxGL7sBmehcEP5vOYvwcKLfMTj9WJMdSupkqiUxzAVotQYM51iaKZ9ZlgviIXzkZGc5l2MvGKiNuPwzS453kQvZqgienh6WPM_AK1uAoNSKPJq_Vt_RxHGiLdnmI0QbBK1Q7vsrqSMtrkyS1Ru2aDDUMdM31aOG0O1pcm1CK3L8Vqc_AoVjCCdBSgAIazmxQrbd3AhJb74n9LgS9Q_wESQDWvo6GYkyWFcBTeT-61JsmdxQuo8VmF76nOdt1QBpg2UpOKuTSAxyjAU4766KN6WmxaUtfdSTeDWdTDUD4YDPTMJ8voYZHnWySM37vXx6oKwYJ1TbWS27drxKKRyfJoKmUn4atB6QUxEOjvS5uSqclSvsrGorB_nSCsORDGh2tyq3YVTWcwMYYKnHP4sVvtPov_BEaG1aQ3wERH3-W0lQOlcYt9Rx0GzAzI2M5lmXcnAuNvU9oNj53TQAjVQha9EyyrlyVjtGnhEvGMAgHrlygTJueojrNpaYFPYmmWgSDKeqUXQ_PTYaV6__w33Cai82EgIvuxFtU6-FbO2G9l24h_2LGOFxK9UAThDjljSAis9Ca0sn8kayxx1v0VbxQGsIu4PE4bdgPPgxkY9EbRuICaUdVu-FXY4rl3ipzWo9YZqOd7aPNaDnCepoqIVr3ULBsHYgV60_9WJe7gO2OzWu7n","summary":[]},{"id":"fc_099a7567dadef391016a59745c12c0819bac7fed3606807308","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading release history window\",\"path\":\".git/logs/HEAD\",\"selector\":\"1448-1470\"}","call_id":"call_BUk8V8IStGkHLL21PZfdAssF","name":"read"},{"id":"fc_099a7567dadef391016a59745c1350819bb9604c69a533eb35","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading release branch history\",\"path\":\".git/logs/refs/heads/release/v0.34.0\",\"selector\":\"raw\"}","call_id":"call_Oz9VXMBZ3DG96XdPhHBilCil","name":"read"},{"id":"fc_099a7567dadef391016a59745c135c819b860e5ff5b5ac5fc1","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading redispatch loop\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"155-430\"}","call_id":"call_0M70HXxGeGtEsQxrDqqUwiJ3","name":"read"},{"id":"fc_099a7567dadef391016a59745c1368819ba561b1676efd7561","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading redispatch tests\",\"path\":\"crates/spt-daemon/tests/redispatch.rs\",\"selector\":\"200-520\"}","call_id":"call_9e0VDa4NuEHOPgiwgQthJjvV","name":"read"},{"id":"fc_099a7567dadef391016a59745c1378819b8563469b6fc31ede","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading refresh integration\",\"path\":\"crates/spt/tests/daemon_refresh_e2e.rs\",\"selector\":\"180-340\"}","call_id":"call_sYyOxv1UgqgkMKZy1Hlq1JTH","name":"read"},{"id":"fc_099a7567dadef391016a59745c138c819b9a78a47fdfbb7c1a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading update apply cycle\",\"path\":\"crates/spt-daemon/src/applyhost.rs\",\"selector\":\"300-390\"}","call_id":"call_hkGjyt1zE28moix9pK6MZCns","name":"read"},{"id":"fc_099a7567dadef391016a59745c1390819b9786b9878fdf862d","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding compatibility delays\",\"pattern\":\"N-1|older broker|predates|fallback|backoff|Duration::from_secs\\\\((15|20|30)\\\\)|BRAIN_WRITE_DEADLINE|CONTROLLER_WRITE_DEADLINE|recv_timeout\",\"path\":\"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/src/brain.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt/tests/daemon_refresh_e2e.rs; docs/adr/0038-broker-stream-lifecycle-truth.md; traceable-reqs.toml\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_IsSj9g4e2EjK4G6922d7eKwK","name":"grep"}]},"duration":17858.809200003743,"ttft":1147.0696000009775,"contextSnapshot":{"promptTokens":82307,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_BUk8V8IStGkHLL21PZfdAssF|fc_099a7567dadef391016a59745c12c0819bac7fed3606807308","toolName":"read","startedAt":"2026-07-17T00:16:28.129Z","args":{"path":".git/logs/HEAD"},"intent":"Reading release history window"},"id":"2b973719","parentId":"6934ca19","timestamp":"2026-07-17T00:16:28.129Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Oz9VXMBZ3DG96XdPhHBilCil|fc_099a7567dadef391016a59745c1350819bb9604c69a533eb35","toolName":"read","startedAt":"2026-07-17T00:16:28.129Z","args":{"path":".git/logs/refs/heads/release/v0.34.0"},"intent":"Reading release branch history"},"id":"49d23148","parentId":"2b973719","timestamp":"2026-07-17T00:16:28.129Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_0M70HXxGeGtEsQxrDqqUwiJ3|fc_099a7567dadef391016a59745c135c819b860e5ff5b5ac5fc1","toolName":"read","startedAt":"2026-07-17T00:16:28.129Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading redispatch loop"},"id":"b31b02ee","parentId":"49d23148","timestamp":"2026-07-17T00:16:28.129Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_9e0VDa4NuEHOPgiwgQthJjvV|fc_099a7567dadef391016a59745c1368819ba561b1676efd7561","toolName":"read","startedAt":"2026-07-17T00:16:28.129Z","args":{"path":"crates/spt-daemon/tests/redispatch.rs"},"intent":"Reading redispatch tests"},"id":"d6d0a8ff","parentId":"b31b02ee","timestamp":"2026-07-17T00:16:28.129Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_sYyOxv1UgqgkMKZy1Hlq1JTH|fc_099a7567dadef391016a59745c1378819b8563469b6fc31ede","toolName":"read","startedAt":"2026-07-17T00:16:28.130Z","args":{"path":"crates/spt/tests/daemon_refresh_e2e.rs"},"intent":"Reading refresh integration"},"id":"a5c26870","parentId":"d6d0a8ff","timestamp":"2026-07-17T00:16:28.130Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_hkGjyt1zE28moix9pK6MZCns|fc_099a7567dadef391016a59745c138c819b9a78a47fdfbb7c1a","toolName":"read","startedAt":"2026-07-17T00:16:28.130Z","args":{"path":"crates/spt-daemon/src/applyhost.rs"},"intent":"Reading update apply cycle"},"id":"85bc5173","parentId":"a5c26870","timestamp":"2026-07-17T00:16:28.130Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_IsSj9g4e2EjK4G6922d7eKwK|fc_099a7567dadef391016a59745c1390819b9786b9878fdf862d","toolName":"grep","startedAt":"2026-07-17T00:16:28.130Z","args":{"path":"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/src/brain.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt/tests/daemon_refresh_e2e.rs; docs/adr/0038-broker-…"},"intent":"Finding compatibility delays"},"id":"cf4d8e65","parentId":"85bc5173","timestamp":"2026-07-17T00:16:28.130Z"}
{"type":"message","id":"f83c51f4","parentId":"cf4d8e65","timestamp":"2026-07-17T00:16:28.135Z","message":{"role":"toolResult","toolCallId":"call_Oz9VXMBZ3DG96XdPhHBilCil|fc_099a7567dadef391016a59745c1350819bb9604c69a533eb35","toolName":"read","content":[{"type":"text","text":"0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tbranch: Created from 01d2d18\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n"}],"details":{"displayContent":{"text":"0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tbranch: Created from 01d2d18\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n","startLine":1,"lineNumbers":[1,2,3]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\refs\\heads\\release\\v0.34.0"}}},"isError":false,"timestamp":1784247388135}}
{"type":"message","id":"c78c1c2c","parentId":"f83c51f4","timestamp":"2026-07-17T00:16:28.139Z","message":{"role":"toolResult","toolCallId":"call_sYyOxv1UgqgkMKZy1Hlq1JTH|fc_099a7567dadef391016a59745c1378819b8563469b6fc31ede","toolName":"read","content":[{"type":"text","text":"fn daemon_refresh_cycles_brain_while_hosted_endpoint_survives() {\n…\n    while Instant::now() < deadline {\n        if status().as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {\n…\n        }\n        std::thread::sleep(Duration::from_millis(150));\n    }\n\n    // ── (5) THE VERB: `spt daemon refresh` — the production CLI path. ──\n    let refresh = {\n        let mut cmd = Command::new(&spt_bin);\n        cmd.no_window()\n            .args([\"daemon\", \"refresh\"])\n            .env(\"SPT_HOME\", home.path());\n        output_bounded(cmd, Duration::from_secs(30))\n    };\n    let refresh_stdout = String::from_utf8_lossy(&refresh.stdout).to_string();\n    let refresh_stderr = String::from_utf8_lossy(&refresh.stderr).to_string();\n\n    // ── (6) The brain must CYCLE: a new pid stamps brain.ready with a bumped\n    //    generation (no binary swapped anywhere). ──\n    let cycled = wait_ready_not(&ready_path, Some(brain_pid_before), Duration::from_secs(30));\n\n    // The broker process must be the SAME OS process (never restarted).\n    let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);\n\n    // The hosted endpoint survives: harness alive + perch back/still ONLINE.\n    let harness_alive_after = harness_pid\n        .map(spt_store::proc::is_process_alive)\n        .unwrap_or(false);\n    let mut still_online = false;\n    let online_deadline = Instant::now() + Duration::from_secs(10);\n    while Instant::now() < online_deadline {\n        if status().as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {\n            still_online = true;\n            break;\n        }\n        std::thread::sleep(Duration::from_millis(150));\n    }\n\n    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();\n    eprintln!(\n        \"=== W4 daemon refresh: run_ok={} online={online} harness_pid={harness_pid:?} \\\n         refresh_ok={} brain_before={brain_pid_before}/g{gen_before} cycled={cycled:?} \\\n         broker_alive={broker_still_running} harness_alive_after={harness_alive_after} \\\n         still_online={still_online} ===\\n--- refresh stdout ---\\n{refresh_stdout}\\n\\\n         --- refresh stderr ---\\n{refresh_stderr}\\n=== endpoint run stderr ===\\n{run_stderr}\\n\\\n         === brain stderr ===\\n{brain_stderr}\",\n        run.status.success(),\n        refresh.status.success(),\n    );\n\n    // ── (7) Reap the ENTIRE tree SCOPED before asserting. ──\n    if let Some(p) = harness_pid {\n        kill_pid(p);\n    }\n    let psyche_perch = perch::resolve_perch_path(&format!(\"{id}-psyche\"), ParentHint::Explicit(id));\n    if let Some(p) = spt_store::info::read_pid(&psyche_perch) {\n        kill_pid(p);\n    }\n    let _ = {\n        let mut cmd = Command::new(&spt_bin);\n        cmd.no_window()\n            .args([\"daemon\", \"stop\", \"--force\"])\n            .env(\"SPT_HOME\", home.path());\n        output_bounded(cmd, Duration::from_secs(20))\n    };\n    if let Some((pid, _)) = cycled {\n        kill_pid(pid);\n    }\n    kill_pid(brain_pid_before);\n    let _ = broker.kill();\n    let _ = broker.wait();\n\n    // ── ASSERTIONS ──\n    assert!(\n        run.status.success(),\n        \"endpoint run --start must succeed: {run_stderr}\"\n    );\n    assert!(\n        online,\n        \"PRECONDITION: the cc endpoint must bind ONLINE before the refresh.\\n\\\n         === brain stderr ===\\n{brain_stderr}\"\n    );\n    assert!(\n        refresh.status.success(),\n        \"REQ-DAEMON-REFRESH: `spt daemon refresh` must exit 0 against a live daemon \\\n         (stdout: {refresh_stdout}; stderr: {refresh_stderr}).\\n\\\n         === brain stderr ===\\n{brain_stderr}\"\n    );\n    let (brain_pid_after, gen_after) = cycled.unwrap_or_else(|| {\n        panic!(\n            \"REQ-DAEMON-REFRESH: the brain must CYCLE (a new pid must stamp brain.ready) \\\n             after refresh — pid stayed {brain_pid_before}.\\n=== brain stderr ===\\n{brain_stderr}\"\n        )\n    });\n    assert_ne!(\n        brain_pid_after, brain_pid_before,\n        \"a fresh brain process must own the ready stamp\"\n    );\n    assert!(\n        gen_after > gen_before,\n        \"REQ-DAEMON-REFRESH: the brain GENERATION must advance across a refresh \\\n         (before g{gen_before}, after g{gen_after}) — the supervisor cycle, not a re-stamp.\\n\\\n         === brain stderr ===\\n{brain_stderr}\"\n    );\n    assert!(\n        broker_still_running,\n        \"REQ-DAEMON-REFRESH: the broker process must survive the refresh untouched \\\n         (pid {broker_pid}).\\n=== brain stderr ===\\n{brain_stderr}\"\n    );\n    assert!(\n        harness_alive_after,\n        \"REQ-DAEMON-REFRESH: the hosted harness session must STILL be ALIVE across the \\\n         refresh — only the brain cycles, never the hosted PTYs. harness_pid={harness_pid:?}.\\n\\\n         === brain stderr ===\\n{brain_stderr}\"\n    );\n    assert!(\n        still_online,\n        \"REQ-DAEMON-REFRESH: the endpoint perch must remain ONLINE across the refresh \\\n         (the session was never torn down).\\n=== brain stderr ===\\n{brain_stderr}\"\n    );\n}\n"}],"details":{"displayContent":{"text":"fn daemon_refresh_cycles_brain_while_hosted_endpoint_survives() {\n…\n    while Instant::now() < deadline {\n        if status().as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {\n…\n        }\r\n        std::thread::sleep(Duration::from_millis(150));\r\n    }\r\n\r\n    // ── (5) THE VERB: `spt daemon refresh` — the production CLI path. ──\r\n    let refresh = {\r\n        let mut cmd = Command::new(&spt_bin);\r\n        cmd.no_window()\r\n            .args([\"daemon\", \"refresh\"])\r\n            .env(\"SPT_HOME\", home.path());\r\n        output_bounded(cmd, Duration::from_secs(30))\r\n    };\r\n    let refresh_stdout = String::from_utf8_lossy(&refresh.stdout).to_string();\r\n    let refresh_stderr = String::from_utf8_lossy(&refresh.stderr).to_string();\r\n\r\n    // ── (6) The brain must CYCLE: a new pid stamps brain.ready with a bumped\r\n    //    generation (no binary swapped anywhere). ──\r\n    let cycled = wait_ready_not(&ready_path, Some(brain_pid_before), Duration::from_secs(30));\r\n\r\n    // The broker process must be the SAME OS process (never restarted).\r\n    let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);\r\n\r\n    // The hosted endpoint survives: harness alive + perch back/still ONLINE.\r\n    let harness_alive_after = harness_pid\r\n        .map(spt_store::proc::is_process_alive)\r\n        .unwrap_or(false);\r\n    let mut still_online = false;\r\n    let online_deadline = Instant::now() + Duration::from_secs(10);\r\n    while Instant::now() < online_deadline {\r\n        if status().as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {\r\n            still_online = true;\r\n            break;\r\n        }\r\n        std::thread::sleep(Duration::from_millis(150));\r\n    }\r\n\r\n    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();\r\n    eprintln!(\r\n        \"=== W4 daemon refresh: run_ok={} online={online} harness_pid={harness_pid:?} \\\r\n         refresh_ok={} brain_before={brain_pid_before}/g{gen_before} cycled={cycled:?} \\\r\n         broker_alive={broker_still_running} harness_alive_after={harness_alive_after} \\\r\n         still_online={still_online} ===\\n--- refresh stdout ---\\n{refresh_stdout}\\n\\\r\n         --- refresh stderr ---\\n{refresh_stderr}\\n=== endpoint run stderr ===\\n{run_stderr}\\n\\\r\n         === brain stderr ===\\n{brain_stderr}\",\r\n        run.status.success(),\r\n        refresh.status.success(),\r\n    );\r\n\r\n    // ── (7) Reap the ENTIRE tree SCOPED before asserting. ──\r\n    if let Some(p) = harness_pid {\r\n        kill_pid(p);\r\n    }\r\n    let psyche_perch = perch::resolve_perch_path(&format!(\"{id}-psyche\"), ParentHint::Explicit(id));\r\n    if let Some(p) = spt_store::info::read_pid(&psyche_perch) {\r\n        kill_pid(p);\r\n    }\r\n    let _ = {\r\n        let mut cmd = Command::new(&spt_bin);\r\n        cmd.no_window()\r\n            .args([\"daemon\", \"stop\", \"--force\"])\r\n            .env(\"SPT_HOME\", home.path());\r\n        output_bounded(cmd, Duration::from_secs(20))\r\n    };\r\n    if let Some((pid, _)) = cycled {\r\n        kill_pid(pid);\r\n    }\r\n    kill_pid(brain_pid_before);\r\n    let _ = broker.kill();\r\n    let _ = broker.wait();\r\n\r\n    // ── ASSERTIONS ──\r\n    assert!(\r\n        run.status.success(),\r\n        \"endpoint run --start must succeed: {run_stderr}\"\r\n    );\r\n    assert!(\r\n        online,\r\n        \"PRECONDITION: the cc endpoint must bind ONLINE before the refresh.\\n\\\r\n         === brain stderr ===\\n{brain_stderr}\"\r\n    );\r\n    assert!(\r\n        refresh.status.success(),\r\n        \"REQ-DAEMON-REFRESH: `spt daemon refresh` must exit 0 against a live daemon \\\r\n         (stdout: {refresh_stdout}; stderr: {refresh_stderr}).\\n\\\r\n         === brain stderr ===\\n{brain_stderr}\"\r\n    );\r\n    let (brain_pid_after, gen_after) = cycled.unwrap_or_else(|| {\r\n        panic!(\r\n            \"REQ-DAEMON-REFRESH: the brain must CYCLE (a new pid must stamp brain.ready) \\\r\n             after refresh — pid stayed {brain_pid_before}.\\n=== brain stderr ===\\n{brain_stderr}\"\r\n        )\r\n    });\r\n    assert_ne!(\r\n        brain_pid_after, brain_pid_before,\r\n        \"a fresh brain process must own the ready stamp\"\r\n    );\r\n    assert!(\r\n        gen_after > gen_before,\r\n        \"REQ-DAEMON-REFRESH: the brain GENERATION must advance across a refresh \\\r\n         (before g{gen_before}, after g{gen_after}) — the supervisor cycle, not a re-stamp.\\n\\\r\n         === brain stderr ===\\n{brain_stderr}\"\r\n    );\r\n    assert!(\r\n        broker_still_running,\r\n        \"REQ-DAEMON-REFRESH: the broker process must survive the refresh untouched \\\r\n         (pid {broker_pid}).\\n=== brain stderr ===\\n{brain_stderr}\"\r\n    );\r\n    assert!(\r\n        harness_alive_after,\r\n        \"REQ-DAEMON-REFRESH: the hosted harness session must STILL be ALIVE across the \\\r\n         refresh — only the brain cycles, never the hosted PTYs. harness_pid={harness_pid:?}.\\n\\\r\n         === brain stderr ===\\n{brain_stderr}\"\r\n    );\r\n    assert!(\r\n        still_online,\r\n        \"REQ-DAEMON-REFRESH: the endpoint perch must remain ONLINE across the refresh \\\r\n         (the session was never torn down).\\n=== brain stderr ===\\n{brain_stderr}\"\r\n    );\r\n}\r\n","startLine":76,"lineNumbers":[76,null,175,176,null,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\tests\\daemon_refresh_e2e.rs"}}},"isError":false,"timestamp":1784247388139}}
{"type":"message","id":"8e0d1008","parentId":"c78c1c2c","timestamp":"2026-07-17T00:16:28.145Z","message":{"role":"toolResult","toolCallId":"call_9e0VDa4NuEHOPgiwgQthJjvV|fc_099a7567dadef391016a59745c1368819ba561b1676efd7561","toolName":"read","content":[{"type":"text","text":"fn render_until(\n…\n) -> bool {\n…\n    while !contains(buf, needle) {\n…\n        match operator.read_event().expect(\"operator read event\") {\n…\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n…\n            }\n            _ => {}\n        }\n    }\n    saw_eof\n}\n\nfn screen_of(viewport: &[u8]) -> String {\n    let mut screen = ScreenGrid::new(24, 80);\n    screen.advance(viewport);\n    String::from_utf8_lossy(&screen.render_repaint()).into_owned()\n}\n\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n// through PRODUCTION rediscovery: a finished historical Attach and a current\n// active Attach on the same endpoint/origin; only the dispatcher generation\n// restarts. The historical stream must neither take nor clear the current\n// controller: post-restart input still renders (the frozen-PTY symptom is\n// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n// clear), the fresh generation logs NO replay of the historical stream id,\n// and the retired row is GONE from the broker's enumeration\n// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n#[test]\nfn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rd1-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n\n    // ── The HISTORICAL attach: drive it, then detach → its exchange is over.\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\n    let mut dec1 = AttachDecoder::new();\n    let (mut cur1, mut view1) = (0u64, Vec::new());\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1\");\n\n    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\n    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n    let end = Instant::now() + Duration::from_secs(20);\n    while peer_stream_ids(&mut probe).contains(&s1_a) {\n        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n        thread::sleep(Duration::from_millis(50));\n    }\n\n    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\n    send_attach_input(&mut operator, s2, b\"CUR-ONE\\r\", 11).expect(\"input s2 pre\");\n    let mut dec2 = AttachDecoder::new();\n    let (mut cur2, mut view2) = (0u64, Vec::new());\n    let eof_pre = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-ONE\", Duration::from_secs(20));\n    assert!(!eof_pre, \"no EOF while gen1 serves the live viewport\");\n    let s2_a = *peer_stream_ids(&mut probe).first().expect(\"s2 row on A\");\n\n    // ── The brain cycle: kill generation 1, start generation 2 — PRODUCTION\n    // rediscovery re-serves what the broker still enumerates.\n    gen1.kill();\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\n\n    // THE regression: input typed AFTER the restart still renders — the\n    // reconstruction re-took the controller (successor re-take parity), and\n    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\n    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\n    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\n    assert!(\n        !eof_post,\n        \"the current controller's stream saw an EOF across the restart — a replayed \\\n         historical detach cleared it (the 7.41 steal)\"\n    );\n    let painted = screen_of(&view2);\n    assert!(painted.contains(\"CUR-ONE\"), \"pre-restart output survived: {painted:?}\");\n    assert!(painted.contains(\"CUR-TWO\"), \"post-restart input/output flows: {painted:?}\");\n\n    // The fresh generation never touched the historical stream id — the\n    // DISPATCH:<id> replay-storm line (the field evidence class) must be\n    // absent for s1, while s2's re-serve is expected work, not noise.\n    let gen2_log = gen2.stderr_log();\n    assert!(\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\n        \"generation 2 replayed the retired historical stream {s1_a}: {gen2_log}\"\n    );\n    let _ = s2_a;\n\n    gen2.kill();\n    let _ = probe.kill_session();\n}\n\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\n// finished-UNRETIRED orderings the Served-report path can never see: the\n// pre-existing backlog and the brain-down-detach window. s1 is attached and\n// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\n// operator detaches (the row finishes with no dispatcher alive; the conn\n// stays open, so the conn-close sweep does not fire either). gen2 starts and\n// serves a fresh current attach: the finished s1 row must be retired\n// UNSERVED (the family-aware terminal check — pre-fix, the pinned opener\n// made this replay PERFECTLY: seeded attach_as steals the current\n// controller, replayed input re-types, replayed EOF clears). The current\n// viewport keeps flowing across several polls, sees no EOF, and gen2's log\n// never carries a DISPATCH:<s1> line; the s1 row leaves the table without\n// ever being served (retire-on-sight drains the backlog).\n// [int->REQ-REDISPATCH-FINISHED-RETIRE]\n#[test]\nfn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rd1b-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n\n    // s1: attached + driven under gen1 (real serve, opener pinned, input in\n    // the ring — everything a replay needs to steal).\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\n    let mut dec1 = AttachDecoder::new();\n    let (mut cur1, mut view1) = (0u64, Vec::new());\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n\n    // The brain-down-detach window: kill gen1 FIRST (no worker can report\n    // Served), THEN detach — the row finishes unretired on a live conn.\n    gen1.kill();\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1 while brain down\");\n    {\n        let end = Instant::now() + Duration::from_secs(10);\n        loop {\n            let finished = probe\n                .net_streams()\n                .expect(\"net-streams\")\n                .streams\n                .iter()\n                .any(|s| s.stream_id == s1_a && s.finished);\n            if finished {\n                break;\n            }\n            assert!(Instant::now() < end, \"s1 never read as finished on A\");\n            thread::sleep(Duration::from_millis(50));\n        }\n    }\n\n    // gen2 + a fresh CURRENT attach. Pre-fix, gen2's replay of s1 steals and\n    // clears this controller within its first poll cycles.\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\n    let mut dec2 = AttachDecoder::new();\n    let (mut cur2, mut view2) = (0u64, Vec::new());\n\n    // Several seconds of live driving across many 50ms dispatcher polls — the\n    // window in which a stale replay would land its steal.\n    for round in 0..3u32 {\n        let marker = format!(\"CUR-{round}\\r\");\n        send_attach_input(&mut operator, s2, marker.as_bytes(), 11 + u64::from(round))\n            .expect(\"input s2\");\n        let eof = render_until(\n            &mut operator,\n            s2,\n            &mut dec2,\n            &mut cur2,\n            &mut view2,\n            format!(\"CUR-{round}\").as_bytes(),\n            Duration::from_secs(20),\n        );\n        assert!(!eof, \"round {round}: the current controller's stream saw an EOF — a stale replay cleared it\");\n        thread::sleep(Duration::from_millis(700));\n    }\n\n    // Retire-on-sight drained the finished row WITHOUT serving it.\n    {\n        let end = Instant::now() + Duration::from_secs(15);\n        while peer_stream_ids(&mut probe).contains(&s1_a) {\n            assert!(Instant::now() < end, \"the finished-unretired row never drained from the table\");\n            thread::sleep(Duration::from_millis(50));\n        }\n    }\n    let gen2_log = gen2.stderr_log();\n    assert!(\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\n        \"gen2 replayed the finished-unretired stream {s1_a}: {gen2_log}\"\n    );\n\n    gen2.kill();\n    let _ = probe.kill_session();\n}\n\n// [int->REQ-REDISPATCH-FINISHED-RETIRE] the GUARD leg for the family-aware\n// discriminator (gate round 1 counter-check): a request/reply stream reads\n// `finished=true` the moment its requester FINs with the request — sync's\n// `request_sync` does exactly that — so a finished row must NOT be blanket-\n// terminal. The request here arrives while NO dispatcher generation is\n// alive (the refresh window), sits finished-unserved, and the next\n// generation must still SERVE it: the pull completes with a reply. A bare\n// `s.finished` claim filter fails this test (context sync dead across every\n// brain cycle).\n#[test]\nfn finished_pending_sync_request_still_serves_across_generation_start() {\n    init_home();\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    // NO generation alive: the pull's Request+FIN lands and sits finished.\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let cs = spt_store::contextstore::ContextStore::open_or_init_in(&dir.path().join(\"cs-b\"))\n        .expect(\"requester context store\");\n    let scratch = dir.path().join(\"scratch-b\");\n\n    let puller = thread::spawn(move || {\n        spt_daemon::sync::request_sync(\n            &mut operator,\n            conn.conn_id,\n            &[\"p-nothing\".to_string()],\n            MintedOp::new(Minter::Cli, 2),\n            &cs,\n            &scratch,\n        )\n    });\n\n    // The row is on A, finished (the requester FIN'd with its Request),\n    // and UNSERVED — exactly the shape a blanket finished-filter kills.\n    let s_a = {\n        let end = Instant::now() + Duration::from_secs(10);\n        loop {\n            if let Some(s) = probe\n                .net_streams()\n                .expect(\"net-streams\")\n                .streams\n                .iter()\n                .find(|s| !s.initiated_locally && s.finished)\n            {\n                break s.stream_id;\n            }\n            assert!(Instant::now() < end, \"the finished pending pull never appeared on A\");\n            thread::sleep(Duration::from_millis(50));\n        }\n    };\n\n    // The generation starts AFTER the request finished — and must serve it.\n    let gen = Generation::spawn(&name_a, dir.path().join(\"gen.log\"), &[]);\n    let report = puller\n        .join()\n        .expect(\"puller thread\")\n        .expect(\"the finished pending pull must be SERVED, not skipped as terminal\");\n    assert!(\n        report.applied.is_empty(),\n        \"an unknown ref applies nothing (the completed reply itself is the assertion): {report:?}\"\n    );\n    let _ = s_a;\n\n    gen.kill();\n}\n\n// [int->REQ-STREAM-OPENER-DURABLE] D2 — the abandoned-active-stream leg: an\n// ACTIVE attach outlives its opener in the bounded ring (test-rig ring cap\n// via SPT_STREAM_RING_CHUNKS — production sizing untouched), the dispatcher\n// generation restarts, and DURABLE classification (the broker-pinned opener,\n// never ring seq 0) resumes the SAME operator stream: post-restart input\n// renders. Pre-fix this classified Unknown/Failed with the claim burned —\n// the stream was permanently abandoned.\n#[test]\nfn opener_evicted_active_stream_resumes_after_dispatcher_restart() {\n    init_home();\n    // Shrink the ring BEFORE the broker starts (read once at NetShared::new).\n    // nextest = one process per test: no cross-test env bleed.\n    std::env::set_var(\"SPT_STREAM_RING_CHUNKS\", \"8\");\n    let dir = tempfile::tempdir().expect(\"tempdir\");\n    let (name_a, name_b) = (unique_name(), unique_name());\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\n\n    let mut probe = connect_retry(&name_a);\n    let sid = probe\n        .spawn_session(echo_spawn_req(\"rd2-ep\"))\n        .expect(\"spawn echo child\");\n    let a_addr = probe.net_status().expect(\"a status\").addr;\n\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n\n    let mut operator = connect_retry(&name_b);\n    let conn = operator\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\n        .expect(\"dial\");\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");\n    send_attach_input(&mut operator, s1, b\"MARK-A\\r\", 10).expect(\"input pre\");\n    let mut dec = AttachDecoder::new();\n    let (mut cur, mut view) = (0u64, Vec::new());\n    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-A\", Duration::from_secs(20));\n…\n}\n\n[Showing lines 199-523 of 621. Use :524 to continue]"}],"details":{"truncation":{"content":"            }\r\n            _ => {}\r\n        }\r\n    }\r\n    saw_eof\r\n}\r\n\r\nfn screen_of(viewport: &[u8]) -> String {\r\n    let mut screen = ScreenGrid::new(24, 80);\r\n    screen.advance(viewport);\r\n    String::from_utf8_lossy(&screen.render_repaint()).into_owned()\r\n}\r\n\r\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\r\n// through PRODUCTION rediscovery: a finished historical Attach and a current\r\n// active Attach on the same endpoint/origin; only the dispatcher generation\r\n// restarts. The historical stream must neither take nor clear the current\r\n// controller: post-restart input still renders (the frozen-PTY symptom is\r\n// the failure), the operator sees NO EOF (the replayed-EOF detach is the\r\n// clear), the fresh generation logs NO replay of the historical stream id,\r\n// and the retired row is GONE from the broker's enumeration\r\n// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\r\n#[test]\r\nfn finished_historical_attach_neither_takes_nor_clears_current_controller() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd1-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n\r\n    // ── The HISTORICAL attach: drive it, then detach → its exchange is over.\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\r\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\r\n    let mut dec1 = AttachDecoder::new();\r\n    let (mut cur1, mut view1) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\r\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\r\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1\");\r\n\r\n    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\r\n    // the enumeration a FRESH dispatcher would replay from — it must shrink.\r\n    let end = Instant::now() + Duration::from_secs(20);\r\n    while peer_stream_ids(&mut probe).contains(&s1_a) {\r\n        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\r\n        thread::sleep(Duration::from_millis(50));\r\n    }\r\n\r\n    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\r\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\r\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\r\n    send_attach_input(&mut operator, s2, b\"CUR-ONE\\r\", 11).expect(\"input s2 pre\");\r\n    let mut dec2 = AttachDecoder::new();\r\n    let (mut cur2, mut view2) = (0u64, Vec::new());\r\n    let eof_pre = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-ONE\", Duration::from_secs(20));\r\n    assert!(!eof_pre, \"no EOF while gen1 serves the live viewport\");\r\n    let s2_a = *peer_stream_ids(&mut probe).first().expect(\"s2 row on A\");\r\n\r\n    // ── The brain cycle: kill generation 1, start generation 2 — PRODUCTION\r\n    // rediscovery re-serves what the broker still enumerates.\r\n    gen1.kill();\r\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\r\n\r\n    // THE regression: input typed AFTER the restart still renders — the\r\n    // reconstruction re-took the controller (successor re-take parity), and\r\n    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\r\n    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\r\n    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\r\n    assert!(\r\n        !eof_post,\r\n        \"the current controller's stream saw an EOF across the restart — a replayed \\\r\n         historical detach cleared it (the 7.41 steal)\"\r\n    );\r\n    let painted = screen_of(&view2);\r\n    assert!(painted.contains(\"CUR-ONE\"), \"pre-restart output survived: {painted:?}\");\r\n    assert!(painted.contains(\"CUR-TWO\"), \"post-restart input/output flows: {painted:?}\");\r\n\r\n    // The fresh generation never touched the historical stream id — the\r\n    // DISPATCH:<id> replay-storm line (the field evidence class) must be\r\n    // absent for s1, while s2's re-serve is expected work, not noise.\r\n    let gen2_log = gen2.stderr_log();\r\n    assert!(\r\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\r\n        \"generation 2 replayed the retired historical stream {s1_a}: {gen2_log}\"\r\n    );\r\n    let _ = s2_a;\r\n\r\n    gen2.kill();\r\n    let _ = probe.kill_session();\r\n}\r\n\r\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\r\n// finished-UNRETIRED orderings the Served-report path can never see: the\r\n// pre-existing backlog and the brain-down-detach window. s1 is attached and\r\n// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\r\n// operator detaches (the row finishes with no dispatcher alive; the conn\r\n// stays open, so the conn-close sweep does not fire either). gen2 starts and\r\n// serves a fresh current attach: the finished s1 row must be retired\r\n// UNSERVED (the family-aware terminal check — pre-fix, the pinned opener\r\n// made this replay PERFECTLY: seeded attach_as steals the current\r\n// controller, replayed input re-types, replayed EOF clears). The current\r\n// viewport keeps flowing across several polls, sees no EOF, and gen2's log\r\n// never carries a DISPATCH:<s1> line; the s1 row leaves the table without\r\n// ever being served (retire-on-sight drains the backlog).\r\n// [int->REQ-REDISPATCH-FINISHED-RETIRE]\r\n#[test]\r\nfn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd1b-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n\r\n    // s1: attached + driven under gen1 (real serve, opener pinned, input in\r\n    // the ring — everything a replay needs to steal).\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\r\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\r\n    let mut dec1 = AttachDecoder::new();\r\n    let (mut cur1, mut view1) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\r\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\r\n\r\n    // The brain-down-detach window: kill gen1 FIRST (no worker can report\r\n    // Served), THEN detach — the row finishes unretired on a live conn.\r\n    gen1.kill();\r\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1 while brain down\");\r\n    {\r\n        let end = Instant::now() + Duration::from_secs(10);\r\n        loop {\r\n            let finished = probe\r\n                .net_streams()\r\n                .expect(\"net-streams\")\r\n                .streams\r\n                .iter()\r\n                .any(|s| s.stream_id == s1_a && s.finished);\r\n            if finished {\r\n                break;\r\n            }\r\n            assert!(Instant::now() < end, \"s1 never read as finished on A\");\r\n            thread::sleep(Duration::from_millis(50));\r\n        }\r\n    }\r\n\r\n    // gen2 + a fresh CURRENT attach. Pre-fix, gen2's replay of s1 steals and\r\n    // clears this controller within its first poll cycles.\r\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\r\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\r\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\r\n    let mut dec2 = AttachDecoder::new();\r\n    let (mut cur2, mut view2) = (0u64, Vec::new());\r\n\r\n    // Several seconds of live driving across many 50ms dispatcher polls — the\r\n    // window in which a stale replay would land its steal.\r\n    for round in 0..3u32 {\r\n        let marker = format!(\"CUR-{round}\\r\");\r\n        send_attach_input(&mut operator, s2, marker.as_bytes(), 11 + u64::from(round))\r\n            .expect(\"input s2\");\r\n        let eof = render_until(\r\n            &mut operator,\r\n            s2,\r\n            &mut dec2,\r\n            &mut cur2,\r\n            &mut view2,\r\n            format!(\"CUR-{round}\").as_bytes(),\r\n            Duration::from_secs(20),\r\n        );\r\n        assert!(!eof, \"round {round}: the current controller's stream saw an EOF — a stale replay cleared it\");\r\n        thread::sleep(Duration::from_millis(700));\r\n    }\r\n\r\n    // Retire-on-sight drained the finished row WITHOUT serving it.\r\n    {\r\n        let end = Instant::now() + Duration::from_secs(15);\r\n        while peer_stream_ids(&mut probe).contains(&s1_a) {\r\n            assert!(Instant::now() < end, \"the finished-unretired row never drained from the table\");\r\n            thread::sleep(Duration::from_millis(50));\r\n        }\r\n    }\r\n    let gen2_log = gen2.stderr_log();\r\n    assert!(\r\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\r\n        \"gen2 replayed the finished-unretired stream {s1_a}: {gen2_log}\"\r\n    );\r\n\r\n    gen2.kill();\r\n    let _ = probe.kill_session();\r\n}\r\n\r\n// [int->REQ-REDISPATCH-FINISHED-RETIRE] the GUARD leg for the family-aware\r\n// discriminator (gate round 1 counter-check): a request/reply stream reads\r\n// `finished=true` the moment its requester FINs with the request — sync's\r\n// `request_sync` does exactly that — so a finished row must NOT be blanket-\r\n// terminal. The request here arrives while NO dispatcher generation is\r\n// alive (the refresh window), sits finished-unserved, and the next\r\n// generation must still SERVE it: the pull completes with a reply. A bare\r\n// `s.finished` claim filter fails this test (context sync dead across every\r\n// brain cycle).\r\n#[test]\r\nfn finished_pending_sync_request_still_serves_across_generation_start() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    // NO generation alive: the pull's Request+FIN lands and sits finished.\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n    let cs = spt_store::contextstore::ContextStore::open_or_init_in(&dir.path().join(\"cs-b\"))\r\n        .expect(\"requester context store\");\r\n    let scratch = dir.path().join(\"scratch-b\");\r\n\r\n    let puller = thread::spawn(move || {\r\n        spt_daemon::sync::request_sync(\r\n            &mut operator,\r\n            conn.conn_id,\r\n            &[\"p-nothing\".to_string()],\r\n            MintedOp::new(Minter::Cli, 2),\r\n            &cs,\r\n            &scratch,\r\n        )\r\n    });\r\n\r\n    // The row is on A, finished (the requester FIN'd with its Request),\r\n    // and UNSERVED — exactly the shape a blanket finished-filter kills.\r\n    let s_a = {\r\n        let end = Instant::now() + Duration::from_secs(10);\r\n        loop {\r\n            if let Some(s) = probe\r\n                .net_streams()\r\n                .expect(\"net-streams\")\r\n                .streams\r\n                .iter()\r\n                .find(|s| !s.initiated_locally && s.finished)\r\n            {\r\n                break s.stream_id;\r\n            }\r\n            assert!(Instant::now() < end, \"the finished pending pull never appeared on A\");\r\n            thread::sleep(Duration::from_millis(50));\r\n        }\r\n    };\r\n\r\n    // The generation starts AFTER the request finished — and must serve it.\r\n    let gen = Generation::spawn(&name_a, dir.path().join(\"gen.log\"), &[]);\r\n    let report = puller\r\n        .join()\r\n        .expect(\"puller thread\")\r\n        .expect(\"the finished pending pull must be SERVED, not skipped as terminal\");\r\n    assert!(\r\n        report.applied.is_empty(),\r\n        \"an unknown ref applies nothing (the completed reply itself is the assertion): {report:?}\"\r\n    );\r\n    let _ = s_a;\r\n\r\n    gen.kill();\r\n}\r\n\r\n// [int->REQ-STREAM-OPENER-DURABLE] D2 — the abandoned-active-stream leg: an\r\n// ACTIVE attach outlives its opener in the bounded ring (test-rig ring cap\r\n// via SPT_STREAM_RING_CHUNKS — production sizing untouched), the dispatcher\r\n// generation restarts, and DURABLE classification (the broker-pinned opener,\r\n// never ring seq 0) resumes the SAME operator stream: post-restart input\r\n// renders. Pre-fix this classified Unknown/Failed with the claim burned —\r\n// the stream was permanently abandoned.\r\n#[test]\r\nfn opener_evicted_active_stream_resumes_after_dispatcher_restart() {\r\n    init_home();\r\n    // Shrink the ring BEFORE the broker starts (read once at NetShared::new).\r\n    // nextest = one process per test: no cross-test env bleed.\r\n    std::env::set_var(\"SPT_STREAM_RING_CHUNKS\", \"8\");\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd2-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");\r\n    send_attach_input(&mut operator, s1, b\"MARK-A\\r\", 10).expect(\"input pre\");\r\n    let mut dec = AttachDecoder::new();\r\n    let (mut cur, mut view) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-A\", Duration::from_secs(20));\r","truncated":true,"truncatedBy":"lines","totalLines":423,"totalBytes":15484,"outputLines":325,"outputBytes":15484,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn render_until(\n…\n) -> bool {\n…\n    while !contains(buf, needle) {\n…\n        match operator.read_event().expect(\"operator read event\") {\n…\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n…\n            }\r\n            _ => {}\r\n        }\r\n    }\r\n    saw_eof\r\n}\r\n\r\nfn screen_of(viewport: &[u8]) -> String {\r\n    let mut screen = ScreenGrid::new(24, 80);\r\n    screen.advance(viewport);\r\n    String::from_utf8_lossy(&screen.render_repaint()).into_owned()\r\n}\r\n\r\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\r\n// through PRODUCTION rediscovery: a finished historical Attach and a current\r\n// active Attach on the same endpoint/origin; only the dispatcher generation\r\n// restarts. The historical stream must neither take nor clear the current\r\n// controller: post-restart input still renders (the frozen-PTY symptom is\r\n// the failure), the operator sees NO EOF (the replayed-EOF detach is the\r\n// clear), the fresh generation logs NO replay of the historical stream id,\r\n// and the retired row is GONE from the broker's enumeration\r\n// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\r\n#[test]\r\nfn finished_historical_attach_neither_takes_nor_clears_current_controller() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd1-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n\r\n    // ── The HISTORICAL attach: drive it, then detach → its exchange is over.\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\r\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\r\n    let mut dec1 = AttachDecoder::new();\r\n    let (mut cur1, mut view1) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\r\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\r\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1\");\r\n\r\n    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\r\n    // the enumeration a FRESH dispatcher would replay from — it must shrink.\r\n    let end = Instant::now() + Duration::from_secs(20);\r\n    while peer_stream_ids(&mut probe).contains(&s1_a) {\r\n        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\r\n        thread::sleep(Duration::from_millis(50));\r\n    }\r\n\r\n    // ── The CURRENT attach: same endpoint, same origin, actively controlled.\r\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\r\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\r\n    send_attach_input(&mut operator, s2, b\"CUR-ONE\\r\", 11).expect(\"input s2 pre\");\r\n    let mut dec2 = AttachDecoder::new();\r\n    let (mut cur2, mut view2) = (0u64, Vec::new());\r\n    let eof_pre = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-ONE\", Duration::from_secs(20));\r\n    assert!(!eof_pre, \"no EOF while gen1 serves the live viewport\");\r\n    let s2_a = *peer_stream_ids(&mut probe).first().expect(\"s2 row on A\");\r\n\r\n    // ── The brain cycle: kill generation 1, start generation 2 — PRODUCTION\r\n    // rediscovery re-serves what the broker still enumerates.\r\n    gen1.kill();\r\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\r\n\r\n    // THE regression: input typed AFTER the restart still renders — the\r\n    // reconstruction re-took the controller (successor re-take parity), and\r\n    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\r\n    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\r\n    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\r\n    assert!(\r\n        !eof_post,\r\n        \"the current controller's stream saw an EOF across the restart — a replayed \\\r\n         historical detach cleared it (the 7.41 steal)\"\r\n    );\r\n    let painted = screen_of(&view2);\r\n    assert!(painted.contains(\"CUR-ONE\"), \"pre-restart output survived: {painted:?}\");\r\n    assert!(painted.contains(\"CUR-TWO\"), \"post-restart input/output flows: {painted:?}\");\r\n\r\n    // The fresh generation never touched the historical stream id — the\r\n    // DISPATCH:<id> replay-storm line (the field evidence class) must be\r\n    // absent for s1, while s2's re-serve is expected work, not noise.\r\n    let gen2_log = gen2.stderr_log();\r\n    assert!(\r\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\r\n        \"generation 2 replayed the retired historical stream {s1_a}: {gen2_log}\"\r\n    );\r\n    let _ = s2_a;\r\n\r\n    gen2.kill();\r\n    let _ = probe.kill_session();\r\n}\r\n\r\n// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\r\n// finished-UNRETIRED orderings the Served-report path can never see: the\r\n// pre-existing backlog and the brain-down-detach window. s1 is attached and\r\n// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\r\n// operator detaches (the row finishes with no dispatcher alive; the conn\r\n// stays open, so the conn-close sweep does not fire either). gen2 starts and\r\n// serves a fresh current attach: the finished s1 row must be retired\r\n// UNSERVED (the family-aware terminal check — pre-fix, the pinned opener\r\n// made this replay PERFECTLY: seeded attach_as steals the current\r\n// controller, replayed input re-types, replayed EOF clears). The current\r\n// viewport keeps flowing across several polls, sees no EOF, and gen2's log\r\n// never carries a DISPATCH:<s1> line; the s1 row leaves the table without\r\n// ever being served (retire-on-sight drains the backlog).\r\n// [int->REQ-REDISPATCH-FINISHED-RETIRE]\r\n#[test]\r\nfn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd1b-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n\r\n    // s1: attached + driven under gen1 (real serve, opener pinned, input in\r\n    // the ring — everything a replay needs to steal).\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request s1\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe s1\");\r\n    send_attach_input(&mut operator, s1, b\"HIST-MARK\\r\", 10).expect(\"input s1\");\r\n    let mut dec1 = AttachDecoder::new();\r\n    let (mut cur1, mut view1) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\r\n    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\r\n\r\n    // The brain-down-detach window: kill gen1 FIRST (no worker can report\r\n    // Served), THEN detach — the row finishes unretired on a live conn.\r\n    gen1.kill();\r\n    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1 while brain down\");\r\n    {\r\n        let end = Instant::now() + Duration::from_secs(10);\r\n        loop {\r\n            let finished = probe\r\n                .net_streams()\r\n                .expect(\"net-streams\")\r\n                .streams\r\n                .iter()\r\n                .any(|s| s.stream_id == s1_a && s.finished);\r\n            if finished {\r\n                break;\r\n            }\r\n            assert!(Instant::now() < end, \"s1 never read as finished on A\");\r\n            thread::sleep(Duration::from_millis(50));\r\n        }\r\n    }\r\n\r\n    // gen2 + a fresh CURRENT attach. Pre-fix, gen2's replay of s1 steals and\r\n    // clears this controller within its first poll cycles.\r\n    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\r\n    let s2 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 3), AttachIntent::Control).expect(\"request s2\");\r\n    operator.net_stream_subscribe(s2, 0).expect(\"subscribe s2\");\r\n    let mut dec2 = AttachDecoder::new();\r\n    let (mut cur2, mut view2) = (0u64, Vec::new());\r\n\r\n    // Several seconds of live driving across many 50ms dispatcher polls — the\r\n    // window in which a stale replay would land its steal.\r\n    for round in 0..3u32 {\r\n        let marker = format!(\"CUR-{round}\\r\");\r\n        send_attach_input(&mut operator, s2, marker.as_bytes(), 11 + u64::from(round))\r\n            .expect(\"input s2\");\r\n        let eof = render_until(\r\n            &mut operator,\r\n            s2,\r\n            &mut dec2,\r\n            &mut cur2,\r\n            &mut view2,\r\n            format!(\"CUR-{round}\").as_bytes(),\r\n            Duration::from_secs(20),\r\n        );\r\n        assert!(!eof, \"round {round}: the current controller's stream saw an EOF — a stale replay cleared it\");\r\n        thread::sleep(Duration::from_millis(700));\r\n    }\r\n\r\n    // Retire-on-sight drained the finished row WITHOUT serving it.\r\n    {\r\n        let end = Instant::now() + Duration::from_secs(15);\r\n        while peer_stream_ids(&mut probe).contains(&s1_a) {\r\n            assert!(Instant::now() < end, \"the finished-unretired row never drained from the table\");\r\n            thread::sleep(Duration::from_millis(50));\r\n        }\r\n    }\r\n    let gen2_log = gen2.stderr_log();\r\n    assert!(\r\n        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\r\n        \"gen2 replayed the finished-unretired stream {s1_a}: {gen2_log}\"\r\n    );\r\n\r\n    gen2.kill();\r\n    let _ = probe.kill_session();\r\n}\r\n\r\n// [int->REQ-REDISPATCH-FINISHED-RETIRE] the GUARD leg for the family-aware\r\n// discriminator (gate round 1 counter-check): a request/reply stream reads\r\n// `finished=true` the moment its requester FINs with the request — sync's\r\n// `request_sync` does exactly that — so a finished row must NOT be blanket-\r\n// terminal. The request here arrives while NO dispatcher generation is\r\n// alive (the refresh window), sits finished-unserved, and the next\r\n// generation must still SERVE it: the pull completes with a reply. A bare\r\n// `s.finished` claim filter fails this test (context sync dead across every\r\n// brain cycle).\r\n#[test]\r\nfn finished_pending_sync_request_still_serves_across_generation_start() {\r\n    init_home();\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    // NO generation alive: the pull's Request+FIN lands and sits finished.\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n    let cs = spt_store::contextstore::ContextStore::open_or_init_in(&dir.path().join(\"cs-b\"))\r\n        .expect(\"requester context store\");\r\n    let scratch = dir.path().join(\"scratch-b\");\r\n\r\n    let puller = thread::spawn(move || {\r\n        spt_daemon::sync::request_sync(\r\n            &mut operator,\r\n            conn.conn_id,\r\n            &[\"p-nothing\".to_string()],\r\n            MintedOp::new(Minter::Cli, 2),\r\n            &cs,\r\n            &scratch,\r\n        )\r\n    });\r\n\r\n    // The row is on A, finished (the requester FIN'd with its Request),\r\n    // and UNSERVED — exactly the shape a blanket finished-filter kills.\r\n    let s_a = {\r\n        let end = Instant::now() + Duration::from_secs(10);\r\n        loop {\r\n            if let Some(s) = probe\r\n                .net_streams()\r\n                .expect(\"net-streams\")\r\n                .streams\r\n                .iter()\r\n                .find(|s| !s.initiated_locally && s.finished)\r\n            {\r\n                break s.stream_id;\r\n            }\r\n            assert!(Instant::now() < end, \"the finished pending pull never appeared on A\");\r\n            thread::sleep(Duration::from_millis(50));\r\n        }\r\n    };\r\n\r\n    // The generation starts AFTER the request finished — and must serve it.\r\n    let gen = Generation::spawn(&name_a, dir.path().join(\"gen.log\"), &[]);\r\n    let report = puller\r\n        .join()\r\n        .expect(\"puller thread\")\r\n        .expect(\"the finished pending pull must be SERVED, not skipped as terminal\");\r\n    assert!(\r\n        report.applied.is_empty(),\r\n        \"an unknown ref applies nothing (the completed reply itself is the assertion): {report:?}\"\r\n    );\r\n    let _ = s_a;\r\n\r\n    gen.kill();\r\n}\r\n\r\n// [int->REQ-STREAM-OPENER-DURABLE] D2 — the abandoned-active-stream leg: an\r\n// ACTIVE attach outlives its opener in the bounded ring (test-rig ring cap\r\n// via SPT_STREAM_RING_CHUNKS — production sizing untouched), the dispatcher\r\n// generation restarts, and DURABLE classification (the broker-pinned opener,\r\n// never ring seq 0) resumes the SAME operator stream: post-restart input\r\n// renders. Pre-fix this classified Unknown/Failed with the claim burned —\r\n// the stream was permanently abandoned.\r\n#[test]\r\nfn opener_evicted_active_stream_resumes_after_dispatcher_restart() {\r\n    init_home();\r\n    // Shrink the ring BEFORE the broker starts (read once at NetShared::new).\r\n    // nextest = one process per test: no cross-test env bleed.\r\n    std::env::set_var(\"SPT_STREAM_RING_CHUNKS\", \"8\");\r\n    let dir = tempfile::tempdir().expect(\"tempdir\");\r\n    let (name_a, name_b) = (unique_name(), unique_name());\r\n    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\r\n    let _broker_b = net_broker(&name_b, &dir.path().join(\"b\"));\r\n\r\n    let mut probe = connect_retry(&name_a);\r\n    let sid = probe\r\n        .spawn_session(echo_spawn_req(\"rd2-ep\"))\r\n        .expect(\"spawn echo child\");\r\n    let a_addr = probe.net_status().expect(\"a status\").addr;\r\n\r\n    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\r\n\r\n    let mut operator = connect_retry(&name_b);\r\n    let conn = operator\r\n        .net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))\r\n        .expect(\"dial\");\r\n    let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\r\n    operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");\r\n    send_attach_input(&mut operator, s1, b\"MARK-A\\r\", 10).expect(\"input pre\");\r\n    let mut dec = AttachDecoder::new();\r\n    let (mut cur, mut view) = (0u64, Vec::new());\r\n    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-A\", Duration::from_secs(20));\r\n…\n}","startLine":160,"lineNumbers":[160,null,168,null,172,null,179,null,197,null,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,null,569]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\redispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":621,"totalBytes":15484,"outputLines":325,"outputBytes":15484,"shownRange":{"start":199,"end":523},"nextOffset":524}}},"isError":false,"timestamp":1784247388145}}
{"type":"message","id":"9cc87e7d","parentId":"8e0d1008","timestamp":"2026-07-17T00:16:28.153Z","message":{"role":"toolResult","toolCallId":"call_0M70HXxGeGtEsQxrDqqUwiJ3|fc_099a7567dadef391016a59745c135c819b860e5ff5b5ac5fc1","toolName":"read","content":[{"type":"text","text":"pub fn classify_first_line(line: &serde_json::Value) -> StreamFamily {\n    match line.get(\"kind\").and_then(|k| k.as_str()) {\n…\n        None => {\n…\n        }\n    }\n}\n\n/// Why a dispatched stream's worker ended — loud + typed, never silent.\n#[derive(Debug)]\npub enum DispatchOutcome {\n    /// The family's serve loop ran to its own outcome (rendered for the log).\n    Served(String),\n    /// The first line never arrived / didn't parse — dropped.\n    Unknown,\n    /// The worker failed (IO against broker or store).\n    Failed(String),\n}\n\n/// A claimed stream's dispatch lifecycle (ADR-0038 C,\n/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n/// insert performed BEFORE worker spawn and never cleared — one transient\n/// worker-setup failure permanently abandoned the stream. Claims now\n/// distinguish in-flight vs terminal vs retryable, and a transient failure\n/// requeues bounded (backoff-shaped, never a hot loop).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum ClaimState {\n    /// A worker owns the stream right now (`attempts` = prior failures).\n    InFlight { attempts: u32 },\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\n    Terminal,\n    /// A transient failure released the claim; eligible again at `next_at`.\n    Retry { attempts: u32, next_at: Instant },\n}\n\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n/// failures recover, a persistently-failing stream goes terminal loud.\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\n\n/// Backoff base for a released claim (doubles per failed attempt).\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n\n/// The backoff before retry number `attempts` (1-based) runs.\nfn retry_backoff(attempts: u32) -> Duration {\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n    match state {\n        None => Some(0),\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n        _ => None,\n    }\n}\n\n/// PURE outcome transition: what a finished worker's claim becomes. Served\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n/// hot-loops. A failure requeues with backoff until the bounded budget is\n/// spent, then goes terminal.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\n    match outcome {\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n        DispatchOutcome::Failed(_) => {\n            let attempts = attempts + 1;\n            if attempts >= MAX_DISPATCH_ATTEMPTS {\n                ClaimState::Terminal\n            } else {\n                ClaimState::Retry {\n                    attempts,\n                    next_at: now + retry_backoff(attempts),\n                }\n            }\n        }\n    }\n}\n\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n/// redispatch — retire unserved, never replay?\n///\n/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\n/// would be a regression: the request/reply families FIN their send WITH the\n/// request and await the reply on the same stream (sync.rs `request_sync` and\n/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\n/// opener), so every pull that arrives while the brain is down reads\n/// `finished=true` while genuinely PENDING — filtering it breaks context sync\n/// across exactly the refresh window this milestone fixes.\n///\n/// The steal class is ATTACH-specific: `finished` on an attach stream is the\n/// operator's clean detach — its exchange is definitionally over, and a\n/// replay takes a live controller (KNOWN-HAZARDS 7.41: the pre-existing\n/// backlog on long-lived conns, and the brain-down-detach window that\n/// Served-report retirement can never see). So: finished + Attach = terminal\n/// (retired unserved via the worker's Served report); finished + anything\n/// else = a pending request — serve it.\n// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\nfn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\n    finished && family == StreamFamily::Attach\n}\n\n/// TEST-RIG-ONLY (the D3 regression): `SPT_DISPATCH_INJECT_TRANSIENT_ONCE`\n/// set at dispatcher start makes exactly ONE worker fail before setup —\n/// proving a transient worker-start failure requeues and recovers through\n/// the REAL claim path. Never set in production; the latch arms once per\n/// process and disarms on first take.\nfn take_injected_transient() -> bool {\n    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\n    let armed = ARMED.get_or_init(|| {\n        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\").is_ok())\n    });\n    armed.swap(false, Ordering::SeqCst)\n}\n\n/// The SPT_HOME-anchored paths the serve arms need. One value, cloned per\n/// worker (policies/stores re-load from the canonical paths per request —\n/// the access-gate stance, config edits take effect without a restart).\n#[derive(Debug, Clone)]\npub struct DispatchPaths {\n    /// The perch tree (WAN message funnel target).\n    pub owlery: PathBuf,\n    /// The staged-release cache dir ([`ReleaseCache::open`]).\n    pub releases: PathBuf,\n    /// Transient bundle/artifact scratch.\n    pub scratch: PathBuf,\n}\n\nimpl DispatchPaths {\n    /// The production layout under [`perch::spt_home`].\n    pub fn production() -> DispatchPaths {\n        let home = perch::spt_home();\n        DispatchPaths {\n            owlery: perch::owlery_dir(),\n            releases: home.join(\"releases\"),\n            scratch: home.join(\"scratch\").join(\"net\"),\n        }\n    }\n}\n\n/// Watch the broker's stream table and dispatch every **peer-initiated**\n/// stream to a family worker, until `stop` is set. Blocks the calling thread\n/// (the daemon spawns it on its own); workers detach (each ends with its\n/// stream).\npub fn run_dispatch_loop(\n    broker_name: &str,\n    paths: &DispatchPaths,\n    registry: Arc<RegistryHost>,\n    stop: &AtomicBool,\n    poll: Duration,\n) -> io::Result<()> {\n    let mut brain = connect(broker_name)?;\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n    // abandoned a stream on the first transient failure.\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n\n    while !stop.load(Ordering::Relaxed) {\n        let reply = brain.net_streams()?;\n        for s in reply.streams {\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\n            // invisible until its first write anyway — gotcha #6 — but the\n            // next_seq guard also skips a row whose pump hasn't appended yet).\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n            if s.initiated_locally || s.next_seq == 0 {\n                continue;\n            }\n            let attempts = {\n                let mut c = claims.lock().unwrap();\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\n                    Some(attempts) => {\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n                        attempts\n                    }\n                    None => continue,\n                }\n            };\n            let name = broker_name.to_string();\n            let origin = s.remote_id_hex.clone();\n            let stream_id = s.stream_id;\n            let floor_seq = s.floor_seq;\n            let finished = s.finished;\n            let p = paths.clone();\n            let reg = Arc::clone(&registry);\n            let claims_done = Arc::clone(&claims);\n            thread::spawn(move || {\n                let outcome = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n                if matches!(outcome, DispatchOutcome::Served(_)) {\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n                    // this stream's exchange is over — take its row out of\n                    // every FUTURE dispatcher generation's enumeration (the\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n                    // an older broker without the verb answers error (KH-2.3)\n                    // and the row retires when the broker cycles.\n                    if let Ok(mut retire_brain) = connect(&name) {\n                        let _ = retire_brain.net_stream_retire(stream_id);\n                    }\n                } else {\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\n                }\n                let mut c = claims_done.lock().unwrap();\n                c.insert(\n                    stream_id,\n                    outcome_transition(&outcome, attempts, Instant::now()),\n                );\n            });\n        }\n        thread::sleep(poll);\n    }\n    Ok(())\n}\n\n/// Connect a fresh brain IPC client (retrying through broker startup).\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start(broker_name, now_ms()) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}\n\nfn now_ms() -> u64 {\n    std::time::SystemTime::now()\n        .duration_since(std::time::UNIX_EPOCH)\n        .map(|d| d.as_millis() as u64)\n        .unwrap_or(0)\n}\n\n/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\n/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\n/// it survives any ring roll — and fall back to the ring peek only when the\n/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n/// (terminal); `Err` = transient (the claim requeues bounded).\nfn first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\n        // Pinned: classify from the durable fact. A pinned line that does not\n        // parse is terminally unclassifiable (same contract as the peek path).\n        Ok(Some(bytes)) => return Ok(serde_json::from_slice(&bytes).ok()),\n        // The broker holds the stream but no opener is pinned yet (first\n        // newline still in flight, or the capture gave up bounded). The\n        // next_seq guard proved bytes exist, so treat as transient — the\n        // bounded claim retry re-reads it shortly, and a stream that never\n        // pins goes terminal when the budget is spent (no hot loop).\n        Ok(None) => {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id}: opener not pinned yet\"\n            )))\n        }\n        // Older broker (unknown verb answers error) or the row vanished —\n        // fall through to the ring peek, the pre-ADR-0038 path.\n        Err(_) => {}\n    }\n    peek_first_line(broker_name, stream_id, floor_seq)\n}\n\n/// Peek the stream's first retained NDJSON line on a throwaway brain\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\n/// gap check.\nfn peek_first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    let mut brain = connect(broker_name)?;\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\n    let mut buf: Vec<u8> = Vec::new();\n    loop {\n        match brain.read_event()? {\n…\n        }\n    }\n}\n\n[Showing lines 154-433 of 1034. Use :434 to continue]"}],"details":{"truncation":{"content":"        }\r\n    }\r\n}\r\n\r\n/// Why a dispatched stream's worker ended — loud + typed, never silent.\r\n#[derive(Debug)]\r\npub enum DispatchOutcome {\r\n    /// The family's serve loop ran to its own outcome (rendered for the log).\r\n    Served(String),\r\n    /// The first line never arrived / didn't parse — dropped.\r\n    Unknown,\r\n    /// The worker failed (IO against broker or store).\r\n    Failed(String),\r\n}\r\n\r\n/// A claimed stream's dispatch lifecycle (ADR-0038 C,\r\n/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\r\n/// insert performed BEFORE worker spawn and never cleared — one transient\r\n/// worker-setup failure permanently abandoned the stream. Claims now\r\n/// distinguish in-flight vs terminal vs retryable, and a transient failure\r\n/// requeues bounded (backoff-shaped, never a hot loop).\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum ClaimState {\r\n    /// A worker owns the stream right now (`attempts` = prior failures).\r\n    InFlight { attempts: u32 },\r\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\r\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\r\n    Terminal,\r\n    /// A transient failure released the claim; eligible again at `next_at`.\r\n    Retry { attempts: u32, next_at: Instant },\r\n}\r\n\r\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\r\n/// failures recover, a persistently-failing stream goes terminal loud.\r\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\r\n\r\n/// Backoff base for a released claim (doubles per failed attempt).\r\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\r\n\r\n/// The backoff before retry number `attempts` (1-based) runs.\r\nfn retry_backoff(attempts: u32) -> Duration {\r\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\r\n}\r\n\r\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\r\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\r\n/// off). The unit-tested seam for the no-hot-loop ruling.\r\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\r\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\r\n    match state {\r\n        None => Some(0),\r\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\r\n        _ => None,\r\n    }\r\n}\r\n\r\n/// PURE outcome transition: what a finished worker's claim becomes. Served\r\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\r\n/// hot-loops. A failure requeues with backoff until the bounded budget is\r\n/// spent, then goes terminal.\r\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\r\nfn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\r\n    match outcome {\r\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\r\n        DispatchOutcome::Failed(_) => {\r\n            let attempts = attempts + 1;\r\n            if attempts >= MAX_DISPATCH_ATTEMPTS {\r\n                ClaimState::Terminal\r\n            } else {\r\n                ClaimState::Retry {\r\n                    attempts,\r\n                    next_at: now + retry_backoff(attempts),\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\r\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\r\n/// redispatch — retire unserved, never replay?\r\n///\r\n/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\r\n/// would be a regression: the request/reply families FIN their send WITH the\r\n/// request and await the reply on the same stream (sync.rs `request_sync` and\r\n/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\r\n/// opener), so every pull that arrives while the brain is down reads\r\n/// `finished=true` while genuinely PENDING — filtering it breaks context sync\r\n/// across exactly the refresh window this milestone fixes.\r\n///\r\n/// The steal class is ATTACH-specific: `finished` on an attach stream is the\r\n/// operator's clean detach — its exchange is definitionally over, and a\r\n/// replay takes a live controller (KNOWN-HAZARDS 7.41: the pre-existing\r\n/// backlog on long-lived conns, and the brain-down-detach window that\r\n/// Served-report retirement can never see). So: finished + Attach = terminal\r\n/// (retired unserved via the worker's Served report); finished + anything\r\n/// else = a pending request — serve it.\r\n// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\r\nfn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\r\n    finished && family == StreamFamily::Attach\r\n}\r\n\r\n/// TEST-RIG-ONLY (the D3 regression): `SPT_DISPATCH_INJECT_TRANSIENT_ONCE`\r\n/// set at dispatcher start makes exactly ONE worker fail before setup —\r\n/// proving a transient worker-start failure requeues and recovers through\r\n/// the REAL claim path. Never set in production; the latch arms once per\r\n/// process and disarms on first take.\r\nfn take_injected_transient() -> bool {\r\n    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\r\n    let armed = ARMED.get_or_init(|| {\r\n        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\").is_ok())\r\n    });\r\n    armed.swap(false, Ordering::SeqCst)\r\n}\r\n\r\n/// The SPT_HOME-anchored paths the serve arms need. One value, cloned per\r\n/// worker (policies/stores re-load from the canonical paths per request —\r\n/// the access-gate stance, config edits take effect without a restart).\r\n#[derive(Debug, Clone)]\r\npub struct DispatchPaths {\r\n    /// The perch tree (WAN message funnel target).\r\n    pub owlery: PathBuf,\r\n    /// The staged-release cache dir ([`ReleaseCache::open`]).\r\n    pub releases: PathBuf,\r\n    /// Transient bundle/artifact scratch.\r\n    pub scratch: PathBuf,\r\n}\r\n\r\nimpl DispatchPaths {\r\n    /// The production layout under [`perch::spt_home`].\r\n    pub fn production() -> DispatchPaths {\r\n        let home = perch::spt_home();\r\n        DispatchPaths {\r\n            owlery: perch::owlery_dir(),\r\n            releases: home.join(\"releases\"),\r\n            scratch: home.join(\"scratch\").join(\"net\"),\r\n        }\r\n    }\r\n}\r\n\r\n/// Watch the broker's stream table and dispatch every **peer-initiated**\r\n/// stream to a family worker, until `stop` is set. Blocks the calling thread\r\n/// (the daemon spawns it on its own); workers detach (each ends with its\r\n/// stream).\r\npub fn run_dispatch_loop(\r\n    broker_name: &str,\r\n    paths: &DispatchPaths,\r\n    registry: Arc<RegistryHost>,\r\n    stop: &AtomicBool,\r\n    poll: Duration,\r\n) -> io::Result<()> {\r\n    let mut brain = connect(broker_name)?;\r\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\r\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\r\n    // abandoned a stream on the first transient failure.\r\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\r\n\r\n    while !stop.load(Ordering::Relaxed) {\r\n        let reply = brain.net_streams()?;\r\n        for s in reply.streams {\r\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\r\n            // invisible until its first write anyway — gotcha #6 — but the\r\n            // next_seq guard also skips a row whose pump hasn't appended yet).\r\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\r\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\r\n            if s.initiated_locally || s.next_seq == 0 {\r\n                continue;\r\n            }\r\n            let attempts = {\r\n                let mut c = claims.lock().unwrap();\r\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\r\n                    Some(attempts) => {\r\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\r\n                        attempts\r\n                    }\r\n                    None => continue,\r\n                }\r\n            };\r\n            let name = broker_name.to_string();\r\n            let origin = s.remote_id_hex.clone();\r\n            let stream_id = s.stream_id;\r\n            let floor_seq = s.floor_seq;\r\n            let finished = s.finished;\r\n            let p = paths.clone();\r\n            let reg = Arc::clone(&registry);\r\n            let claims_done = Arc::clone(&claims);\r\n            thread::spawn(move || {\r\n                let outcome = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\r\n                if matches!(outcome, DispatchOutcome::Served(_)) {\r\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\r\n                    // this stream's exchange is over — take its row out of\r\n                    // every FUTURE dispatcher generation's enumeration (the\r\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\r\n                    // an older broker without the verb answers error (KH-2.3)\r\n                    // and the row retires when the broker cycles.\r\n                    if let Ok(mut retire_brain) = connect(&name) {\r\n                        let _ = retire_brain.net_stream_retire(stream_id);\r\n                    }\r\n                } else {\r\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\r\n                }\r\n                let mut c = claims_done.lock().unwrap();\r\n                c.insert(\r\n                    stream_id,\r\n                    outcome_transition(&outcome, attempts, Instant::now()),\r\n                );\r\n            });\r\n        }\r\n        thread::sleep(poll);\r\n    }\r\n    Ok(())\r\n}\r\n\r\n/// Connect a fresh brain IPC client (retrying through broker startup).\r\nfn connect(broker_name: &str) -> io::Result<Brain> {\r\n    let mut last = io::Error::other(\"broker never answered\");\r\n    for _ in 0..300 {\r\n        match Brain::cold_start(broker_name, now_ms()) {\r\n            Ok(b) => return Ok(b),\r\n            Err(e) => last = e,\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    Err(last)\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    std::time::SystemTime::now()\r\n        .duration_since(std::time::UNIX_EPOCH)\r\n        .map(|d| d.as_millis() as u64)\r\n        .unwrap_or(0)\r\n}\r\n\r\n/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\r\n/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\r\n/// it survives any ring roll — and fall back to the ring peek only when the\r\n/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\r\n/// (terminal); `Err` = transient (the claim requeues bounded).\r\nfn first_line(\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    floor_seq: u64,\r\n) -> io::Result<Option<serde_json::Value>> {\r\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\r\n        // Pinned: classify from the durable fact. A pinned line that does not\r\n        // parse is terminally unclassifiable (same contract as the peek path).\r\n        Ok(Some(bytes)) => return Ok(serde_json::from_slice(&bytes).ok()),\r\n        // The broker holds the stream but no opener is pinned yet (first\r\n        // newline still in flight, or the capture gave up bounded). The\r\n        // next_seq guard proved bytes exist, so treat as transient — the\r\n        // bounded claim retry re-reads it shortly, and a stream that never\r\n        // pins goes terminal when the budget is spent (no hot loop).\r\n        Ok(None) => {\r\n            return Err(io::Error::other(format!(\r\n                \"stream {stream_id}: opener not pinned yet\"\r\n            )))\r\n        }\r\n        // Older broker (unknown verb answers error) or the row vanished —\r\n        // fall through to the ring peek, the pre-ADR-0038 path.\r\n        Err(_) => {}\r\n    }\r\n    peek_first_line(broker_name, stream_id, floor_seq)\r\n}\r\n\r\n/// Peek the stream's first retained NDJSON line on a throwaway brain\r\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\r\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\r\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\r\n/// gap check.\r\nfn peek_first_line(\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    floor_seq: u64,\r\n) -> io::Result<Option<serde_json::Value>> {\r\n    let mut brain = connect(broker_name)?;\r\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\r\n    let mut buf: Vec<u8> = Vec::new();\r\n    loop {\r\n        match brain.read_event()? {\r","truncated":true,"truncatedBy":"lines","totalLines":881,"totalBytes":12395,"outputLines":280,"outputBytes":12395,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"pub fn classify_first_line(line: &serde_json::Value) -> StreamFamily {\n    match line.get(\"kind\").and_then(|k| k.as_str()) {\n…\n        None => {\n…\n        }\r\n    }\r\n}\r\n\r\n/// Why a dispatched stream's worker ended — loud + typed, never silent.\r\n#[derive(Debug)]\r\npub enum DispatchOutcome {\r\n    /// The family's serve loop ran to its own outcome (rendered for the log).\r\n    Served(String),\r\n    /// The first line never arrived / didn't parse — dropped.\r\n    Unknown,\r\n    /// The worker failed (IO against broker or store).\r\n    Failed(String),\r\n}\r\n\r\n/// A claimed stream's dispatch lifecycle (ADR-0038 C,\r\n/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\r\n/// insert performed BEFORE worker spawn and never cleared — one transient\r\n/// worker-setup failure permanently abandoned the stream. Claims now\r\n/// distinguish in-flight vs terminal vs retryable, and a transient failure\r\n/// requeues bounded (backoff-shaped, never a hot loop).\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum ClaimState {\r\n    /// A worker owns the stream right now (`attempts` = prior failures).\r\n    InFlight { attempts: u32 },\r\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\r\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\r\n    Terminal,\r\n    /// A transient failure released the claim; eligible again at `next_at`.\r\n    Retry { attempts: u32, next_at: Instant },\r\n}\r\n\r\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\r\n/// failures recover, a persistently-failing stream goes terminal loud.\r\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\r\n\r\n/// Backoff base for a released claim (doubles per failed attempt).\r\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\r\n\r\n/// The backoff before retry number `attempts` (1-based) runs.\r\nfn retry_backoff(attempts: u32) -> Duration {\r\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\r\n}\r\n\r\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\r\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\r\n/// off). The unit-tested seam for the no-hot-loop ruling.\r\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\r\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\r\n    match state {\r\n        None => Some(0),\r\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\r\n        _ => None,\r\n    }\r\n}\r\n\r\n/// PURE outcome transition: what a finished worker's claim becomes. Served\r\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\r\n/// hot-loops. A failure requeues with backoff until the bounded budget is\r\n/// spent, then goes terminal.\r\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\r\nfn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\r\n    match outcome {\r\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\r\n        DispatchOutcome::Failed(_) => {\r\n            let attempts = attempts + 1;\r\n            if attempts >= MAX_DISPATCH_ATTEMPTS {\r\n                ClaimState::Terminal\r\n            } else {\r\n                ClaimState::Retry {\r\n                    attempts,\r\n                    next_at: now + retry_backoff(attempts),\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\r\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\r\n/// redispatch — retire unserved, never replay?\r\n///\r\n/// `finished` alone is NOT the answer, and a bare `s.finished` claim filter\r\n/// would be a regression: the request/reply families FIN their send WITH the\r\n/// request and await the reply on the same stream (sync.rs `request_sync` and\r\n/// propagate.rs `fetch_update` both `net_stream_send(.., finish=true)` on the\r\n/// opener), so every pull that arrives while the brain is down reads\r\n/// `finished=true` while genuinely PENDING — filtering it breaks context sync\r\n/// across exactly the refresh window this milestone fixes.\r\n///\r\n/// The steal class is ATTACH-specific: `finished` on an attach stream is the\r\n/// operator's clean detach — its exchange is definitionally over, and a\r\n/// replay takes a live controller (KNOWN-HAZARDS 7.41: the pre-existing\r\n/// backlog on long-lived conns, and the brain-down-detach window that\r\n/// Served-report retirement can never see). So: finished + Attach = terminal\r\n/// (retired unserved via the worker's Served report); finished + anything\r\n/// else = a pending request — serve it.\r\n// [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n// [impl->REQ-HAZARD-REDISPATCH-CONTROL-STEAL]\r\nfn finished_row_is_terminal(family: StreamFamily, finished: bool) -> bool {\r\n    finished && family == StreamFamily::Attach\r\n}\r\n\r\n/// TEST-RIG-ONLY (the D3 regression): `SPT_DISPATCH_INJECT_TRANSIENT_ONCE`\r\n/// set at dispatcher start makes exactly ONE worker fail before setup —\r\n/// proving a transient worker-start failure requeues and recovers through\r\n/// the REAL claim path. Never set in production; the latch arms once per\r\n/// process and disarms on first take.\r\nfn take_injected_transient() -> bool {\r\n    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\r\n    let armed = ARMED.get_or_init(|| {\r\n        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\").is_ok())\r\n    });\r\n    armed.swap(false, Ordering::SeqCst)\r\n}\r\n\r\n/// The SPT_HOME-anchored paths the serve arms need. One value, cloned per\r\n/// worker (policies/stores re-load from the canonical paths per request —\r\n/// the access-gate stance, config edits take effect without a restart).\r\n#[derive(Debug, Clone)]\r\npub struct DispatchPaths {\r\n    /// The perch tree (WAN message funnel target).\r\n    pub owlery: PathBuf,\r\n    /// The staged-release cache dir ([`ReleaseCache::open`]).\r\n    pub releases: PathBuf,\r\n    /// Transient bundle/artifact scratch.\r\n    pub scratch: PathBuf,\r\n}\r\n\r\nimpl DispatchPaths {\r\n    /// The production layout under [`perch::spt_home`].\r\n    pub fn production() -> DispatchPaths {\r\n        let home = perch::spt_home();\r\n        DispatchPaths {\r\n            owlery: perch::owlery_dir(),\r\n            releases: home.join(\"releases\"),\r\n            scratch: home.join(\"scratch\").join(\"net\"),\r\n        }\r\n    }\r\n}\r\n\r\n/// Watch the broker's stream table and dispatch every **peer-initiated**\r\n/// stream to a family worker, until `stop` is set. Blocks the calling thread\r\n/// (the daemon spawns it on its own); workers detach (each ends with its\r\n/// stream).\r\npub fn run_dispatch_loop(\r\n    broker_name: &str,\r\n    paths: &DispatchPaths,\r\n    registry: Arc<RegistryHost>,\r\n    stop: &AtomicBool,\r\n    poll: Duration,\r\n) -> io::Result<()> {\r\n    let mut brain = connect(broker_name)?;\r\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\r\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\r\n    // abandoned a stream on the first transient failure.\r\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\r\n\r\n    while !stop.load(Ordering::Relaxed) {\r\n        let reply = brain.net_streams()?;\r\n        for s in reply.streams {\r\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\r\n            // invisible until its first write anyway — gotcha #6 — but the\r\n            // next_seq guard also skips a row whose pump hasn't appended yet).\r\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\r\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\r\n            if s.initiated_locally || s.next_seq == 0 {\r\n                continue;\r\n            }\r\n            let attempts = {\r\n                let mut c = claims.lock().unwrap();\r\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\r\n                    Some(attempts) => {\r\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\r\n                        attempts\r\n                    }\r\n                    None => continue,\r\n                }\r\n            };\r\n            let name = broker_name.to_string();\r\n            let origin = s.remote_id_hex.clone();\r\n            let stream_id = s.stream_id;\r\n            let floor_seq = s.floor_seq;\r\n            let finished = s.finished;\r\n            let p = paths.clone();\r\n            let reg = Arc::clone(&registry);\r\n            let claims_done = Arc::clone(&claims);\r\n            thread::spawn(move || {\r\n                let outcome = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\r\n                if matches!(outcome, DispatchOutcome::Served(_)) {\r\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\r\n                    // this stream's exchange is over — take its row out of\r\n                    // every FUTURE dispatcher generation's enumeration (the\r\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\r\n                    // an older broker without the verb answers error (KH-2.3)\r\n                    // and the row retires when the broker cycles.\r\n                    if let Ok(mut retire_brain) = connect(&name) {\r\n                        let _ = retire_brain.net_stream_retire(stream_id);\r\n                    }\r\n                } else {\r\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\r\n                }\r\n                let mut c = claims_done.lock().unwrap();\r\n                c.insert(\r\n                    stream_id,\r\n                    outcome_transition(&outcome, attempts, Instant::now()),\r\n                );\r\n            });\r\n        }\r\n        thread::sleep(poll);\r\n    }\r\n    Ok(())\r\n}\r\n\r\n/// Connect a fresh brain IPC client (retrying through broker startup).\r\nfn connect(broker_name: &str) -> io::Result<Brain> {\r\n    let mut last = io::Error::other(\"broker never answered\");\r\n    for _ in 0..300 {\r\n        match Brain::cold_start(broker_name, now_ms()) {\r\n            Ok(b) => return Ok(b),\r\n            Err(e) => last = e,\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    Err(last)\r\n}\r\n\r\nfn now_ms() -> u64 {\r\n    std::time::SystemTime::now()\r\n        .duration_since(std::time::UNIX_EPOCH)\r\n        .map(|d| d.as_millis() as u64)\r\n        .unwrap_or(0)\r\n}\r\n\r\n/// The stream's first NDJSON line, restart-durable (ADR-0038 B,\r\n/// REQ-STREAM-OPENER-DURABLE): read the broker-pinned opener fact first —\r\n/// it survives any ring roll — and fall back to the ring peek only when the\r\n/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\r\n/// (terminal); `Err` = transient (the claim requeues bounded).\r\nfn first_line(\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    floor_seq: u64,\r\n) -> io::Result<Option<serde_json::Value>> {\r\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\r\n        // Pinned: classify from the durable fact. A pinned line that does not\r\n        // parse is terminally unclassifiable (same contract as the peek path).\r\n        Ok(Some(bytes)) => return Ok(serde_json::from_slice(&bytes).ok()),\r\n        // The broker holds the stream but no opener is pinned yet (first\r\n        // newline still in flight, or the capture gave up bounded). The\r\n        // next_seq guard proved bytes exist, so treat as transient — the\r\n        // bounded claim retry re-reads it shortly, and a stream that never\r\n        // pins goes terminal when the budget is spent (no hot loop).\r\n        Ok(None) => {\r\n            return Err(io::Error::other(format!(\r\n                \"stream {stream_id}: opener not pinned yet\"\r\n            )))\r\n        }\r\n        // Older broker (unknown verb answers error) or the row vanished —\r\n        // fall through to the ring peek, the pre-ADR-0038 path.\r\n        Err(_) => {}\r\n    }\r\n    peek_first_line(broker_name, stream_id, floor_seq)\r\n}\r\n\r\n/// Peek the stream's first retained NDJSON line on a throwaway brain\r\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\r\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\r\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\r\n/// gap check.\r\nfn peek_first_line(\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    floor_seq: u64,\r\n) -> io::Result<Option<serde_json::Value>> {\r\n    let mut brain = connect(broker_name)?;\r\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\r\n    let mut buf: Vec<u8> = Vec::new();\r\n    loop {\r\n        match brain.read_event()? {\r\n…\n        }\n    }\n}","startLine":120,"lineNumbers":[120,121,null,138,null,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,null,451,452,453]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1034,"totalBytes":12395,"outputLines":280,"outputBytes":12395,"shownRange":{"start":154,"end":433},"nextOffset":434}}},"isError":false,"timestamp":1784247388152}}
{"type":"message","id":"5f8752dd","parentId":"9cc87e7d","timestamp":"2026-07-17T00:16:28.167Z","message":{"role":"toolResult","toolCallId":"call_hkGjyt1zE28moix9pK6MZCns|fc_099a7567dadef391016a59745c138c819b9a78a47fdfbb7c1a","toolName":"read","content":[{"type":"text","text":"/// the caller uses [`apply_staged_daemonless`] instead — this path REQUIRES a live\n/// broker to hand off to (a dead broker fails the apply with the seat untouched).\n// [impl->REQ-UPD-3]\n// [impl->REQ-UPD-4]\npub fn apply_staged(\n    cache: &ReleaseCache,\n    release_keys: &Path,\n    exe_path: &Path,\n    broker_name: &str,\n) -> Result<ApplyStagedOutcome, String> {\n    let (version, product_version, artifact) = match prepare_apply(cache, release_keys)? {\n        Prepared::Done(outcome) => return Ok(outcome),\n        Prepared::Proceed {\n            version,\n            product_version,\n            artifact,\n        } => (version, product_version, artifact),\n    };\n\n    // Connect to the broker BEFORE the binary moves — a dead broker fails the\n    // apply with the seat untouched. The connection is the channel the planned\n    // brain-restart trigger rides after the swap (ADR-0018 D3-3); it does not\n    // attach to any session (the broker holds every hosted endpoint across the\n    // brain cycle — that is the whole point).\n    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\n\n    swap_and_record(cache, exe_path, version, &artifact)?;\n\n    // REQ-UPD-3's moment (ADR-0018 D3-3): trigger a real brain-PROCESS restart.\n    // The broker signals its supervisor to cycle the brain child onto the\n    // just-swapped binary with `start-reason=update`; new code runs immediately,\n    // no manual bounce, every broker-held endpoint untouched at the process\n    // level. This replaces the old in-process `apply_brain_only` handoff (the\n    // regression: it re-attached a CLI-side subscriber while the live brain kept\n    // running the OLD code). `classify` already gated this as brain-only, so the\n    // broker never moves; only the brain restarts.\n    //\n    // A broker without a supervisor wired (`honored: false`) still swapped the\n    // binary — the next daemon start uses the new executable. Readiness-gated\n    // promotion + auto-rollback on a brain that fails to come up is D6.\n    match brain.request_brain_restart() {\n        Ok(_honored) => Ok(ApplyStagedOutcome::AppliedPending {\n            version,\n            product_version,\n        }),\n        Err(e) => Err(format!(\n            \"binary swapped to v{version}, but the brain-restart trigger failed: {e} — the broker \\\n             and every hosted endpoint are intact; retry `spt update apply` (an older broker that \\\n             predates the restart verb needs a one-time manual daemon restart to run the new code)\"\n        )),\n    }\n}\n\n/// Cycle the brain of a RUNNING daemon with NO binary swap and NO staging\n/// preconditions (REQ-DAEMON-REFRESH, `spt daemon refresh`): connect to the\n/// broker and raise the same supervisor brain-restart signal the post-swap\n/// apply rides ([`Brain::request_brain_restart`], ADR-0018 D3-3) — brain stop →\n/// respawn → readiness trial → promote, broker + every hosted PTY untouched by\n/// construction. The recovery verb for wedged brain-held state that previously\n/// needed a full daemon bounce. Nothing is verified, staged, or renamed: the\n/// respawn lands on the SAME on-disk binary. A candidate that fails readiness\n/// rides the existing trial rollback semantics unchanged. `Ok(honored)`\n/// mirrors the apply path: `false` means the broker has no supervisor wired\n/// (accepted, not acted on).\n// [impl->REQ-DAEMON-REFRESH]\npub fn refresh_brain(broker_name: &str) -> Result<bool, String> {\n    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\n    brain\n        .request_brain_restart()\n        .map_err(|e| format!(\"brain-restart trigger: {e}\"))\n}\n\n/// Apply the staged release with NO daemon running (REQ-UPDATE-ONE-SHOT-FINISH):\n/// swap the binary + record the trial, and touch NO broker. This is the fix for\n/// the operator wart where `update apply` on a stopped box booted the OLD broker\n/// pre-swap (`ensure_daemon_announced`) just to hand off to it — guaranteeing a\n/// mixed old-broker/new-brain pair and a manual bounce. Here nothing is booted:\n/// the swapped bytes run when the daemon is NEXT started (either by the operator,\n/// or by `update apply --finish` which restarts it in the same command, riding\n/// REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL so hosted endpoints come back). The\n/// recorded `AppliedPending` promotes on that fresh brain's `ready`, exactly as\n/// the handoff path's does. Runs the identical verify/classify gate as\n/// [`apply_staged`], so every fail-closed refusal is preserved.\n// [impl->REQ-UPDATE-ONE-SHOT-FINISH]\n// [impl->REQ-UPD-4]\npub fn apply_staged_daemonless(\n    cache: &ReleaseCache,\n    release_keys: &Path,\n    exe_path: &Path,\n) -> Result<ApplyStagedOutcome, String> {\n    let (version, product_version, artifact) = match prepare_apply(cache, release_keys)? {\n        Prepared::Done(outcome) => return Ok(outcome),\n        Prepared::Proceed {\n            version,\n            product_version,\n…\n        } => (version, product_version, artifact),\n    };\n…\n}\n\n[Showing lines 299-393 of 1183. Use :394 to continue]"}],"details":{"truncation":{"content":"/// the caller uses [`apply_staged_daemonless`] instead — this path REQUIRES a live\r\n/// broker to hand off to (a dead broker fails the apply with the seat untouched).\r\n// [impl->REQ-UPD-3]\r\n// [impl->REQ-UPD-4]\r\npub fn apply_staged(\r\n    cache: &ReleaseCache,\r\n    release_keys: &Path,\r\n    exe_path: &Path,\r\n    broker_name: &str,\r\n) -> Result<ApplyStagedOutcome, String> {\r\n    let (version, product_version, artifact) = match prepare_apply(cache, release_keys)? {\r\n        Prepared::Done(outcome) => return Ok(outcome),\r\n        Prepared::Proceed {\r\n            version,\r\n            product_version,\r\n            artifact,\r\n        } => (version, product_version, artifact),\r\n    };\r\n\r\n    // Connect to the broker BEFORE the binary moves — a dead broker fails the\r\n    // apply with the seat untouched. The connection is the channel the planned\r\n    // brain-restart trigger rides after the swap (ADR-0018 D3-3); it does not\r\n    // attach to any session (the broker holds every hosted endpoint across the\r\n    // brain cycle — that is the whole point).\r\n    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\r\n\r\n    swap_and_record(cache, exe_path, version, &artifact)?;\r\n\r\n    // REQ-UPD-3's moment (ADR-0018 D3-3): trigger a real brain-PROCESS restart.\r\n    // The broker signals its supervisor to cycle the brain child onto the\r\n    // just-swapped binary with `start-reason=update`; new code runs immediately,\r\n    // no manual bounce, every broker-held endpoint untouched at the process\r\n    // level. This replaces the old in-process `apply_brain_only` handoff (the\r\n    // regression: it re-attached a CLI-side subscriber while the live brain kept\r\n    // running the OLD code). `classify` already gated this as brain-only, so the\r\n    // broker never moves; only the brain restarts.\r\n    //\r\n    // A broker without a supervisor wired (`honored: false`) still swapped the\r\n    // binary — the next daemon start uses the new executable. Readiness-gated\r\n    // promotion + auto-rollback on a brain that fails to come up is D6.\r\n    match brain.request_brain_restart() {\r\n        Ok(_honored) => Ok(ApplyStagedOutcome::AppliedPending {\r\n            version,\r\n            product_version,\r\n        }),\r\n        Err(e) => Err(format!(\r\n            \"binary swapped to v{version}, but the brain-restart trigger failed: {e} — the broker \\\r\n             and every hosted endpoint are intact; retry `spt update apply` (an older broker that \\\r\n             predates the restart verb needs a one-time manual daemon restart to run the new code)\"\r\n        )),\r\n    }\r\n}\r\n\r\n/// Cycle the brain of a RUNNING daemon with NO binary swap and NO staging\r\n/// preconditions (REQ-DAEMON-REFRESH, `spt daemon refresh`): connect to the\r\n/// broker and raise the same supervisor brain-restart signal the post-swap\r\n/// apply rides ([`Brain::request_brain_restart`], ADR-0018 D3-3) — brain stop →\r\n/// respawn → readiness trial → promote, broker + every hosted PTY untouched by\r\n/// construction. The recovery verb for wedged brain-held state that previously\r\n/// needed a full daemon bounce. Nothing is verified, staged, or renamed: the\r\n/// respawn lands on the SAME on-disk binary. A candidate that fails readiness\r\n/// rides the existing trial rollback semantics unchanged. `Ok(honored)`\r\n/// mirrors the apply path: `false` means the broker has no supervisor wired\r\n/// (accepted, not acted on).\r\n// [impl->REQ-DAEMON-REFRESH]\r\npub fn refresh_brain(broker_name: &str) -> Result<bool, String> {\r\n    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\r\n    brain\r\n        .request_brain_restart()\r\n        .map_err(|e| format!(\"brain-restart trigger: {e}\"))\r\n}\r\n\r\n/// Apply the staged release with NO daemon running (REQ-UPDATE-ONE-SHOT-FINISH):\r\n/// swap the binary + record the trial, and touch NO broker. This is the fix for\r\n/// the operator wart where `update apply` on a stopped box booted the OLD broker\r\n/// pre-swap (`ensure_daemon_announced`) just to hand off to it — guaranteeing a\r\n/// mixed old-broker/new-brain pair and a manual bounce. Here nothing is booted:\r\n/// the swapped bytes run when the daemon is NEXT started (either by the operator,\r\n/// or by `update apply --finish` which restarts it in the same command, riding\r\n/// REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL so hosted endpoints come back). The\r\n/// recorded `AppliedPending` promotes on that fresh brain's `ready`, exactly as\r\n/// the handoff path's does. Runs the identical verify/classify gate as\r\n/// [`apply_staged`], so every fail-closed refusal is preserved.\r\n// [impl->REQ-UPDATE-ONE-SHOT-FINISH]\r\n// [impl->REQ-UPD-4]\r\npub fn apply_staged_daemonless(\r\n    cache: &ReleaseCache,\r\n    release_keys: &Path,\r\n    exe_path: &Path,\r\n) -> Result<ApplyStagedOutcome, String> {\r\n    let (version, product_version, artifact) = match prepare_apply(cache, release_keys)? {\r\n        Prepared::Done(outcome) => return Ok(outcome),\r\n        Prepared::Proceed {\r\n            version,\r\n            product_version,\r","truncated":true,"truncatedBy":"lines","totalLines":885,"totalBytes":5068,"outputLines":95,"outputBytes":5068,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"/// the caller uses [`apply_staged_daemonless`] instead — this path REQUIRES a live\r\n/// broker to hand off to (a dead broker fails the apply with the seat untouched).\r\n// [impl->REQ-UPD-3]\r\n// [impl->REQ-UPD-4]\r\npub fn apply_staged(\r\n    cache: &ReleaseCache,\r\n    release_keys: &Path,\r\n    exe_path: &Path,\r\n    broker_name: &str,\r\n) -> Result<ApplyStagedOutcome, String> {\r\n    let (version, product_version, artifact) = match prepare_apply(cache, release_keys)? {\r\n        Prepared::Done(outcome) => return Ok(outcome),\r\n        Prepared::Proceed {\r\n            version,\r\n            product_version,\r\n            artifact,\r\n        } => (version, product_version, artifact),\r\n    };\r\n\r\n    // Connect to the broker BEFORE the binary moves — a dead broker fails the\r\n    // apply with the seat untouched. The connection is the channel the planned\r\n    // brain-restart trigger rides after the swap (ADR-0018 D3-3); it does not\r\n    // attach to any session (the broker holds every hosted endpoint across the\r\n    // brain cycle — that is the whole point).\r\n    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\r\n\r\n    swap_and_record(cache, exe_path, version, &artifact)?;\r\n\r\n    // REQ-UPD-3's moment (ADR-0018 D3-3): trigger a real brain-PROCESS restart.\r\n    // The broker signals its supervisor to cycle the brain child onto the\r\n    // just-swapped binary with `start-reason=update`; new code runs immediately,\r\n    // no manual bounce, every broker-held endpoint untouched at the process\r\n    // level. This replaces the old in-process `apply_brain_only` handoff (the\r\n    // regression: it re-attached a CLI-side subscriber while the live brain kept\r\n    // running the OLD code). `classify` already gated this as brain-only, so the\r\n    // broker never moves; only the brain restarts.\r\n    //\r\n    // A broker without a supervisor wired (`honored: false`) still swapped the\r\n    // binary — the next daemon start uses the new executable. Readiness-gated\r\n    // promotion + auto-rollback on a brain that fails to come up is D6.\r\n    match brain.request_brain_restart() {\r\n        Ok(_honored) => Ok(ApplyStagedOutcome::AppliedPending {\r\n            version,\r\n            product_version,\r\n        }),\r\n        Err(e) => Err(format!(\r\n            \"binary swapped to v{version}, but the brain-restart trigger failed: {e} — the broker \\\r\n             and every hosted endpoint are intact; retry `spt update apply` (an older broker that \\\r\n             predates the restart verb needs a one-time manual daemon restart to run the new code)\"\r\n        )),\r\n    }\r\n}\r\n\r\n/// Cycle the brain of a RUNNING daemon with NO binary swap and NO staging\r\n/// preconditions (REQ-DAEMON-REFRESH, `spt daemon refresh`): connect to the\r\n/// broker and raise the same supervisor brain-restart signal the post-swap\r\n/// apply rides ([`Brain::request_brain_restart`], ADR-0018 D3-3) — brain stop →\r\n/// respawn → readiness trial → promote, broker + every hosted PTY untouched by\r\n/// construction. The recovery verb for wedged brain-held state that previously\r\n/// needed a full daemon bounce. Nothing is verified, staged, or renamed: the\r\n/// respawn lands on the SAME on-disk binary. A candidate that fails readiness\r\n/// rides the existing trial rollback semantics unchanged. `Ok(honored)`\r\n/// mirrors the apply path: `false` means the broker has no supervisor wired\r\n/// (accepted, not acted on).\r\n// [impl->REQ-DAEMON-REFRESH]\r\npub fn refresh_brain(broker_name: &str) -> Result<bool, String> {\r\n    let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;\r\n    brain\r\n        .request_brain_restart()\r\n        .map_err(|e| format!(\"brain-restart trigger: {e}\"))\r\n}\r\n\r\n/// Apply the staged release with NO daemon running (REQ-UPDATE-ONE-SHOT-FINISH):\r\n/// swap the binary + record the trial, and touch NO broker. This is the fix for\r\n/// the operator wart where `update apply` on a stopped box booted the OLD broker\r\n/// pre-swap (`ensure_daemon_announced`) just to hand off to it — guaranteeing a\r\n/// mixed old-broker/new-brain pair and a manual bounce. Here nothing is booted:\r\n/// the swapped bytes run when the daemon is NEXT started (either by the operator,\r\n/// or by `update apply --finish` which restarts it in the same command, riding\r\n/// REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL so hosted endpoints come back). The\r\n/// recorded `AppliedPending` promotes on that fresh brain's `ready`, exactly as\r\n/// the handoff path's does. Runs the identical verify/classify gate as\r\n/// [`apply_staged`], so every fail-closed refusal is preserved.\r\n// [impl->REQ-UPDATE-ONE-SHOT-FINISH]\r\n// [impl->REQ-UPD-4]\r\npub fn apply_staged_daemonless(\r\n    cache: &ReleaseCache,\r\n    release_keys: &Path,\r\n    exe_path: &Path,\r\n) -> Result<ApplyStagedOutcome, String> {\r\n    let (version, product_version, artifact) = match prepare_apply(cache, release_keys)? {\r\n        Prepared::Done(outcome) => return Ok(outcome),\r\n        Prepared::Proceed {\r\n            version,\r\n            product_version,\r\n…\n        } => (version, product_version, artifact),\n    };\n…\n}","startLine":299,"lineNumbers":[299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,null,395,396,null,402]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\applyhost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1183,"totalBytes":5068,"outputLines":95,"outputBytes":5068,"shownRange":{"start":299,"end":393},"nextOffset":394}}},"isError":false,"timestamp":1784247388167}}
{"type":"message","id":"851db7a7","parentId":"5f8752dd","timestamp":"2026-07-17T00:16:28.189Z","message":{"role":"toolResult","toolCallId":"call_BUk8V8IStGkHLL21PZfdAssF|fc_099a7567dadef391016a59745c12c0819bac7fed3606807308","toolName":"read","content":[{"type":"text","text":"53bae678013031f01391c68e736f730f5bda148e afd01692c68827e87f9569d899a7b9efc6137d83 Reavo End <decidel@gmail.com> 1784185248 -0700\tcommit (merge): Merge remote-tracking branch 'origin/main' into build/project-index-w3-reader-cutover\nafd01692c68827e87f9569d899a7b9efc6137d83 d88fec6696ea080c32107dc727f5155738e2f2a2 Reavo End <decidel@gmail.com> 1784186564 -0700\tcommit: test(project-index-w3): reader e2e fixture commits ep2's alpha slice; re-home the PICKER-HISTORY-FRESH impl tag\nd88fec6696ea080c32107dc727f5155738e2f2a2 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784188140 -0700\tcheckout: moving from build/project-index-w3-reader-cutover to main\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188142 -0700\tpull --ff-only origin main: Fast-forward\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188143 -0700\tcheckout: moving from main to main\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700\tcheckout: moving from main to release/v0.33.0\n34ae823b2ada1c5098b195d6e5f70422f0d7098d 42d06a87401a62d2a8a6a81ccff2532d43ca6ede Reavo End <decidel@gmail.com> 1784192217 -0700\tcommit: release: v0.33.0 (counter 60) — PROJECT-INDEX milestone\n42d06a87401a62d2a8a6a81ccff2532d43ca6ede 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tcheckout: moving from release/v0.33.0 to main\n21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197583 -0700\tcheckout: moving from main to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197660 -0700\tcheckout: moving from main to build/redispatch-truth-w1\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200151 -0700\tcommit: build(redispatch-truth-w1): broker stream-table lifecycle truth — retire, durable opener, retryable claims\nad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201516 -0700\tcommit: fix(redispatch-truth-w1): gate round 1 — finished ATTACH rows are terminal-unserved; request/reply FIN stays servable\n2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204304 -0700\tcommit: fix(redispatch-truth-w1): gate round 2 — retirement is eligibility-visibility, never teardown\n84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204456 -0700\tcommit: docs(redispatch-truth-w1): ADR-0038 — pin the round-2 retirement ruling (eligibility-visibility, never teardown)\n571d6a55e9949ce1b96aef1ba00f5e007a0a9521 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784206456 -0700\tcheckout: moving from build/redispatch-truth-w1 to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tcheckout: moving from main to release/v0.34.0\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\neb9fd8643921930ce21cc7ef0751a27360fc14bc 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784211926 -0700\tcheckout: moving from release/v0.34.0 to main\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\nb7b074c2509c1015fe341185662b4622d661f274 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784236538 -0700\tcheckout: moving from main to fix/changelog-0330-heading\nb7b074c2509c1015fe341185662b4622d661f274 d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 Reavo End <decidel@gmail.com> 1784236564 -0700\tcommit: docs(changelog): restore consumed '## [0.33.0]' heading\nd0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 37f0b38862a876e38d2ff4793746f8991ac7d961 Reavo End <decidel@gmail.com> 1784236637 -0700\tcommit: docs(runbook): add contiguous-ladder self-check after new CHANGELOG section\n37f0b38862a876e38d2ff4793746f8991ac7d961 9a83afa84ad62d7aaa3866e158927fd929ff3dee Reavo End <decidel@gmail.com> 1784239407 -0700\tcommit: triage(mesh-recovery): milestone JIT — ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first\n9a83afa84ad62d7aaa3866e158927fd929ff3dee b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784239433 -0700\tcheckout: moving from fix/changelog-0330-heading to main\nb7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700\tmerge origin/main: Fast-forward\n\n[Showing lines 1447-1473 of 1484. Use :1474 to continue]"}],"details":{"truncation":{"content":"53bae678013031f01391c68e736f730f5bda148e afd01692c68827e87f9569d899a7b9efc6137d83 Reavo End <decidel@gmail.com> 1784185248 -0700\tcommit (merge): Merge remote-tracking branch 'origin/main' into build/project-index-w3-reader-cutover\nafd01692c68827e87f9569d899a7b9efc6137d83 d88fec6696ea080c32107dc727f5155738e2f2a2 Reavo End <decidel@gmail.com> 1784186564 -0700\tcommit: test(project-index-w3): reader e2e fixture commits ep2's alpha slice; re-home the PICKER-HISTORY-FRESH impl tag\nd88fec6696ea080c32107dc727f5155738e2f2a2 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784188140 -0700\tcheckout: moving from build/project-index-w3-reader-cutover to main\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188142 -0700\tpull --ff-only origin main: Fast-forward\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188143 -0700\tcheckout: moving from main to main\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700\tcheckout: moving from main to release/v0.33.0\n34ae823b2ada1c5098b195d6e5f70422f0d7098d 42d06a87401a62d2a8a6a81ccff2532d43ca6ede Reavo End <decidel@gmail.com> 1784192217 -0700\tcommit: release: v0.33.0 (counter 60) — PROJECT-INDEX milestone\n42d06a87401a62d2a8a6a81ccff2532d43ca6ede 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tcheckout: moving from release/v0.33.0 to main\n21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197583 -0700\tcheckout: moving from main to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197660 -0700\tcheckout: moving from main to build/redispatch-truth-w1\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200151 -0700\tcommit: build(redispatch-truth-w1): broker stream-table lifecycle truth — retire, durable opener, retryable claims\nad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201516 -0700\tcommit: fix(redispatch-truth-w1): gate round 1 — finished ATTACH rows are terminal-unserved; request/reply FIN stays servable\n2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204304 -0700\tcommit: fix(redispatch-truth-w1): gate round 2 — retirement is eligibility-visibility, never teardown\n84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204456 -0700\tcommit: docs(redispatch-truth-w1): ADR-0038 — pin the round-2 retirement ruling (eligibility-visibility, never teardown)\n571d6a55e9949ce1b96aef1ba00f5e007a0a9521 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784206456 -0700\tcheckout: moving from build/redispatch-truth-w1 to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tcheckout: moving from main to release/v0.34.0\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\neb9fd8643921930ce21cc7ef0751a27360fc14bc 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784211926 -0700\tcheckout: moving from release/v0.34.0 to main\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\nb7b074c2509c1015fe341185662b4622d661f274 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784236538 -0700\tcheckout: moving from main to fix/changelog-0330-heading\nb7b074c2509c1015fe341185662b4622d661f274 d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 Reavo End <decidel@gmail.com> 1784236564 -0700\tcommit: docs(changelog): restore consumed '## [0.33.0]' heading\nd0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 37f0b38862a876e38d2ff4793746f8991ac7d961 Reavo End <decidel@gmail.com> 1784236637 -0700\tcommit: docs(runbook): add contiguous-ladder self-check after new CHANGELOG section\n37f0b38862a876e38d2ff4793746f8991ac7d961 9a83afa84ad62d7aaa3866e158927fd929ff3dee Reavo End <decidel@gmail.com> 1784239407 -0700\tcommit: triage(mesh-recovery): milestone JIT — ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first\n9a83afa84ad62d7aaa3866e158927fd929ff3dee b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784239433 -0700\tcheckout: moving from fix/changelog-0330-heading to main\nb7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700\tmerge origin/main: Fast-forward","truncated":true,"truncatedBy":"lines","totalLines":38,"totalBytes":5338,"outputLines":27,"outputBytes":5338,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"53bae678013031f01391c68e736f730f5bda148e afd01692c68827e87f9569d899a7b9efc6137d83 Reavo End <decidel@gmail.com> 1784185248 -0700\tcommit (merge): Merge remote-tracking branch 'origin/main' into build/project-index-w3-reader-cutover\nafd01692c68827e87f9569d899a7b9efc6137d83 d88fec6696ea080c32107dc727f5155738e2f2a2 Reavo End <decidel@gmail.com> 1784186564 -0700\tcommit: test(project-index-w3): reader e2e fixture commits ep2's alpha slice; re-home the PICKER-HISTORY-FRESH impl tag\nd88fec6696ea080c32107dc727f5155738e2f2a2 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784188140 -0700\tcheckout: moving from build/project-index-w3-reader-cutover to main\ne0387e637d175f2aef8d4d1a5e65761be0cd2cf1 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188142 -0700\tpull --ff-only origin main: Fast-forward\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188143 -0700\tcheckout: moving from main to main\n648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700\tcheckout: moving from main to release/v0.33.0\n34ae823b2ada1c5098b195d6e5f70422f0d7098d 42d06a87401a62d2a8a6a81ccff2532d43ca6ede Reavo End <decidel@gmail.com> 1784192217 -0700\tcommit: release: v0.33.0 (counter 60) — PROJECT-INDEX milestone\n42d06a87401a62d2a8a6a81ccff2532d43ca6ede 21a46d5560819e505c83f0908b7b562d65b7d1ac Reavo End <decidel@gmail.com> 1784197500 -0700\tcheckout: moving from release/v0.33.0 to main\n21a46d5560819e505c83f0908b7b562d65b7d1ac 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197511 -0700\tcommit: triage(redispatch-truth): milestone JIT — ADR-0038 + 4 REQs registry-first\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197583 -0700\tcheckout: moving from main to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197660 -0700\tcheckout: moving from main to build/redispatch-truth-w1\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200151 -0700\tcommit: build(redispatch-truth-w1): broker stream-table lifecycle truth — retire, durable opener, retryable claims\nad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201516 -0700\tcommit: fix(redispatch-truth-w1): gate round 1 — finished ATTACH rows are terminal-unserved; request/reply FIN stays servable\n2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204304 -0700\tcommit: fix(redispatch-truth-w1): gate round 2 — retirement is eligibility-visibility, never teardown\n84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204456 -0700\tcommit: docs(redispatch-truth-w1): ADR-0038 — pin the round-2 retirement ruling (eligibility-visibility, never teardown)\n571d6a55e9949ce1b96aef1ba00f5e007a0a9521 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784206456 -0700\tcheckout: moving from build/redispatch-truth-w1 to main\n3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tcheckout: moving from main to release/v0.34.0\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\neb9fd8643921930ce21cc7ef0751a27360fc14bc 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784211926 -0700\tcheckout: moving from release/v0.34.0 to main\n01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\nb7b074c2509c1015fe341185662b4622d661f274 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784236538 -0700\tcheckout: moving from main to fix/changelog-0330-heading\nb7b074c2509c1015fe341185662b4622d661f274 d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 Reavo End <decidel@gmail.com> 1784236564 -0700\tcommit: docs(changelog): restore consumed '## [0.33.0]' heading\nd0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 37f0b38862a876e38d2ff4793746f8991ac7d961 Reavo End <decidel@gmail.com> 1784236637 -0700\tcommit: docs(runbook): add contiguous-ladder self-check after new CHANGELOG section\n37f0b38862a876e38d2ff4793746f8991ac7d961 9a83afa84ad62d7aaa3866e158927fd929ff3dee Reavo End <decidel@gmail.com> 1784239407 -0700\tcommit: triage(mesh-recovery): milestone JIT — ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first\n9a83afa84ad62d7aaa3866e158927fd929ff3dee b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784239433 -0700\tcheckout: moving from fix/changelog-0330-heading to main\nb7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700\tmerge origin/main: Fast-forward","startLine":1447,"lineNumbers":[1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs\\HEAD"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1484,"totalBytes":5338,"outputLines":27,"outputBytes":5338,"shownRange":{"start":1447,"end":1473},"nextOffset":1474}}},"isError":false,"timestamp":1784247388189}}
{"type":"message","id":"e009f7ca","parentId":"851db7a7","timestamp":"2026-07-17T00:16:28.207Z","message":{"role":"toolResult","toolCallId":"call_IsSj9g4e2EjK4G6922d7eKwK|fc_099a7567dadef391016a59745c1390819b9786b9878fdf862d","toolName":"grep","content":[{"type":"text","text":"# traceable-reqs.toml\n 33|id = \"REQ-ARCH-3\"\n*34|title = \"Wire-protocol version independent of crate semver, N-1 compat window\"\n 35|required_stages = [\"impl\", \"unit\"]  # activated M0: spt-proto wire version + compat window (T6)\n 36|[[requirements]]\n 37|id = \"REQ-ARCH-4\"\n...\n 42|[[requirements]]\n*43|id = \"REQ-DAEMON-1\"\n 44|title = \"One per-machine spt-daemon owning all per-machine state\"\n 45|required_stages = [\"impl\", \"unit\", \"int\"]  # int activated M3b-B9: the daemon E2E (spawn→Psyche-loop→commune→brain-restart-survives→graceful-signoff) drives the consolidated daemon-hosted lifecycle through the real IPC\n 46|[[requirements]]\n...\n 63|title = \"spt-store::BranchStore (git branch as versioned KV; commit=checkpoint/tip=resume, atomic multi-key, merge-native sync) is the substrate for coarse/durable/audited state (context, registry snapshot+distribution, daemon checkpoint); hot paths (B5 fsync journal) + indexed queries (SQLite spool) excluded (ADR-0011)\"\n*64|required_stages = [\"impl\", \"unit\"]  # activated M4-D6a: spt_store::branchstore::BranchStore — bare seed repo + linked worktrees (relative-path config, repair fallback), ensure_branch = parentless empty-tree seed (independent KV roots; sync joins with --allow-unrelated-histories at first contact), commit_in_worktree = ONE commit per write incl. atomic multi-key (no-op mints no empty checkpoint), tip/read_at_tip = the recovery read. Git-CLI-backed per ADR-0013 (bounded runner gitrun.rs, hazard 5.3; ident...\n 65|\n 66|# ───────────────────────────── Harness contract — manifest seams ────────────\n 67|[[requirements]]\n...\n 88|id = \"REQ-MANIFEST-6\"\n*89|title = \"Cross-adapter fallback target addressing (M12-W3-T3.2): a cross-adapter fallback target is addressed as `<adapter>:<profile>` (not just a bare adapter_name), resolved through the one composite-addressing resolver (registry::resolve_option) at every adapter-option read site so a fallback may select a shipped/local profile (e.g. a `ccs` profile). CONTEXT.md §cross-adapter-fallback reconciled (\\\"ccs is a profile; cross-adapter fallback may target <adapter>:<profile>\\\"). Contract-only this mileston...\n 90|required_stages = [\"doc\", \"unit\"]  # activated M12-W3 (contract-only: no int)\n 91|[[requirements]]\n 92|id = \"REQ-MANIFEST-7\"\n*93|title = \"Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the `spt endpoint run` picker bakes into the generated `<basename>-<id>` launcher shortcut (REQ-RUN-SHORTCUT). Absent ⇒ the harness-agnostic default `spt` (→ `spt-<id>`); an adapter sets it to brand its shortcuts (claude-spt → `cc` → `cc-<id>`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it f...\n 94|required_stages = [\"doc\", \"impl\", \"unit\"]  # activated M12-W2 follow-on\n 95|[[requirements]]\n 96|id = \"REQ-MANIFEST-8\"\n*97|title = \"[adapter] host_binaries declares the harness executable basenames a kind=\\\"harness\\\" adapter hosts agents inside (e.g. host_binaries = [\\\"claude\\\"]); bind-time pid→exe-basename match (case-insensitive, .exe-stripped) over the seed's parent_pid selects the candidate adapter set; zero matches → a friendly error naming the binary + the --adapter escape hatch. Additive + N-1-safe: optional Vec<String>, #[serde(default, skip_serializing_if = \\\"Vec::is_empty\\\")] (omitted-serialized like shortcut_b...\n 98|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # activated v0.9.0 W2: doc = CONTEXT.md §179 host_binaries candidate rule + docs/MANIFEST.md + docs-site harness-contract host_binaries field. impl = the `[adapter] host_binaries` field (manifest.rs, serde-default omit-empty) + spt_store::proc::exe_basename (Win QueryFullProcessImageNameW / Linux /proc/pid/exe) + the resolver's host_binaries candidate filter (spt-runtime resolve). unit = host_binaries_optional_and_n1_safe (manifest) + exe_basename_resolv...\n 99|[[requirements]]\n 100|id = \"REQ-SEAM-SPAWN\"\n...\n 207|id = \"REQ-EP-8\"\n*208|title = \"AlwaysOnEndpoint: a resident, addressable, mindless endpoint whose adapter binary the daemon supervises continuously — register-triggered by an adapter-option's `[always-on]` manifest section, one supervised binary per `<adapter>[:profile]`, running independent of agent liveness. It self-manages its `#`-addressed channel endpoints via the existing `api bind` (one connection fronts many). The SECOND class of spt-core-boot-launched third-party binary (after the shell wake-watcher); supervision r...\n 209|required_stages = []  # rule 5: design ratified 2026-06-21 (grill-with-docs, ADR-0023); UNBUILT — activate when the milestone delivering [always-on] supervision lands. Reuses shellwake.rs scaffolding (REQ-SHELL-2) minus the offline flip; first consumer is the downstream spt-discord adapter (own repo).\n 210|[[requirements]]\n 211|id = \"REQ-EP-9\"\n...\n 222|title = \"Per-node files, synced Psyche mind\"\n*223|required_stages = [\"impl\", \"unit\"]  # activated M4-D6a: the mind now lives in the tracked/ two-tier store (spt-store::contextstore over BranchStore), physically separate from never-synced node-local perches/ — spt_live::ingest::route_slices routes commune drops' <live-context>/<project-context> slices into a-<id>/p-<project_id> branch files (untagged body → live, the parser fallback; precedence-guarded 6.5; checkpoint commit per written tier); signoff::write_resume_commune stamps Self provenance per ...\n 224|[[requirements]]\n 225|id = \"REQ-INST-3\"\n 226|title = \"Dormant (warm) / suspended (cold) resting states\"\n...\n 238|title = \"Deferred messages not delivered to dormant/suspended instances\"\n*239|required_stages = [\"impl\", \"unit\", \"int\"]  # activated M5-D5a/b: the gate is state-keyed at the hook-channel consumer sites — resting::deferred_held reads the owner's durable rest record at drain time; \"held\" = (deferred=1) ∧ (Dormant|Suspended); poll_drain + cmd_worker_poll gated (the KH 1.4 \"all sites agree\" sweep — shellchan peek ungated: no deferred writer targets shell perches; ring's ephemeral perch recordless by construction = legacy fallback). Release is by construction at the wake edge: th...\n 240|[[requirements]]\n 241|id = \"REQ-INST-7\"\n 242|title = \"Subnet registry + bare-id resolution policy\"\n...\n 291|id = \"REQ-MSG-1\"\n*292|title = \"Local message delivery: TCP-first to a registered address, spool fallback when offline; id->address via registry (stale-clean first); reply routing (__REPLY_TO__)\"\n 293|required_stages = [\"impl\", \"unit\", \"int\"]  # activated M1 T1 (impl+unit); int added T9: killer-quickstart cross-perch E2E\n 294|[[requirements]]\n 295|id = \"REQ-MSG-2\"\n...\n 311|id = \"REQ-MSG-5\"\n*312|title = \"user-msg envelope kind + daemon identity gate: a Gateway endpoint / the local user's CLI author user-msg (the user's authority); agent-family senders re-stamped to plain msg; identity-gated never payload-trusted (KH 7.3/7.5); wire-additive (N-1 receivers tolerate the new type)\"\n*313|required_stages = [\"doc\", \"impl\", \"unit\"]  # activated 2026-06-13: M9-T4 (CONTEXT.md §Gateway user-msg; spt-proto EVENT_TYPE_USER_MSG + compose_user_msg_event + the MsgOrigin identity gate / gate_user_msg_type truth table incl. spoof rows; wire-additive N-1 tolerance fixture). Daemon origination/WAN-ingress wiring + render rides the same activation.\n 314|[[requirements]]\n 315|id = \"REQ-MSG-6\"\n*316|title = \"cross-node Gateway user-msg honored via advertised endpoint_type: a user-msg from a Gateway-typed origin survives the receive_wan funnel as user-msg (vs the fail-closed re-stamp), keyed on the QUIC-handshake-proven origin node (never wire `from`). Trust boundary = subnet membership (operator-ratified 2026-06-13); no defense against an in-subnet member forging the type. Instance.endpoint_type is an additive serde-default field extending REQ-INST-7's data model. Absent/unknown type → re-stamp (N...\n 317|required_stages = [\"doc\", \"impl\", \"unit\"]  # activated 2026-06-13: M9 cross-node Gateway WAN. doc = CONTEXT.md §Gateway posture rewrite (commit 30fea7f); impl = Instance.endpoint_type field (T1) + advertise populate (T2) + receive_wan origin resolve (T3); unit = the origin matrix in wan.rs + advertise-populate assertion (T4). doyle gate PASS 2026-06-13 (PR #9). INT DEFERRED, REQUIRED-BEFORE-LECTURN (tracked gate condition): the T5 twohost HONORED-path int (Gateway advert on A → user-msg honored at B; ...\n 318|\n 319|# ───────────────────────────── Networking / pairing ─────────────────────────\n...\n 334|title = \"Cross-node Psyche sync over P2P replaces gh-repo-sync\"\n*335|required_stages = [\"impl\", \"unit\"]  # activated M4-D6c: git-native bundle sync (ADR-0013). D6c-1 store core: BranchStore bundle plumbing (create_bundle ^have incremental w/ unknown-have full fallback + up-to-date skip; fetch_bundle quarantines tips under refs/spt-sync/ — incoming can never clobber refs/heads before the driver rules; merge_commit_in_worktree two-parent join, FF for all-accepted propagation) + spt_store::syncmerge::apply_fetched — per-file vector verdicts (dominate→accept verbatim/do...\n 336|[[requirements]]\n 337|id = \"REQ-PAIR-1\"\n 338|title = \"TOTP-seeded SPAKE2 pairing\"\n...\n 359|title = \"Elevation-gated per-subnet code fetch (UAC/root or elevated agent; else authenticator app)\"\n*360|required_stages = [\"impl\", \"unit\"]  # activated M4-D2g: elevation.rs probes OS privilege (Linux libc::geteuid()==0; Windows GetTokenInformation(TokenElevation) via windows-sys; cfg-split) — only the binary pulls OS deps. gate_for: Elevated => Show, NotElevated|Unknown => Fallback (fail safe, never leak the code unconfirmed). Refused fetch leaks no code, persists no mint, distinct exit 3, and the fallback names the authenticator app (seed provisioned there at pairing). unit = gate decision both ways + U...\n 361|[[requirements]]\n 362|id = \"REQ-PAIR-7\"\n 363|title = \"Subnet icon (inline image metadata, GUI-only consumer)\"\n...\n 384|title = \"spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)\"\n*385|required_stages = [\"impl\", \"unit\", \"int\"]  # activated M7 D1 (2026-06-05); int added M7 D5: quickstart_e2e asserts the how-to texts name the commands the published guide's prompt blocks rely on. M7 D1/D4 (plan decision 12). Visible command, Agent-commands help group. Topics v1: `ready` (background-task guidance, --once fallback loop, reply mechanics) + `send` (send/ring, reply via plain send to the structural `from`, SENT vs QUEUED); bare `how-to` lists topics. Single source = the binary — docs-site ne...\n 386|\n 387|# ── Endpoint access control (ADR-0009, 2026-06-02; activate M4 node-tier) ──\n 388|[[requirements]]\n...\n 432|id = \"REQ-UPD-8\"\n*433|title = \"Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3....\n*434|required_stages = [\"impl\", \"unit\"]  # activate v0.3.2. impl = fetch set-staging (cli) + apply_staged single platform-guard (applyhost) + ReleaseCache platform stamp (relcache) + product_version metadata (release.rs) + xtask release-publish emits/uploads update-set.json stamped with product_version. unit = (a) fetch update-set.json + per-asset URL/triple derivation; (b) apply REFUSES a platform-mismatched / unstamped single (the regression that bricked hfenduleam) + a matching-stamp single applies; (c) th...\n 435|[[requirements]]\n 436|id = \"REQ-UPD-9\"\n 437|title = \"`gh_release` adapter [update] avenue (optional signing): an adapter declares `[update] avenue = \\\"gh_release\\\", repo = \\\"user/repo\\\"` (+ optional `asset`, default `adapter.spt`; + optional Ed25519 `signing_key`); spt-core's ripple compares the repo's LATEST GitHub release version against the installed adapter version and, when newer, auto-updates by fetching the release `.spt` archive (the REQ-INSTALL-9 `--release` fetch primitive) → verifies the `.spt` against `signing_key` if declared, else ...\n...\n 462|id = \"REQ-TERM-5\"\n*463|title = \"Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnosti...\n 464|# End-state target [doc, impl, unit, int]; activated per-wave as evidence lands\n 465|# (rule 2: traceable-reqs check EXIT=0 every commit — stages match evidence, never\n 466|# pre-activated). doc landed with the MANIFEST [digest] section; int landed Wave 4 E2E.\n...\n 486|[[requirements]]\n*487|id = \"REQ-HOST-RUN-1\"\n 488|title = \"spt-hosted harness bringup: `spt endpoint run` spawns an adapter's `[session.self]` command template into a broker-held PTY (the spawn-session seam, brain.rs spawn_session_pid — same broker path shellhost.rs launch_shell_brokered_in uses for shells, now for kind=\\\"harness\\\" self-role), registers the perch under the given endpoint id, returns the id. Reverses today's harness-hosted-only launch (external launcher → `api bind`). Non-interactive flag set (--adapter <a[:profile]> --id <id> --crea...\n 489|required_stages = [\"impl\", \"unit\", \"int\"]  # activated M12-W1: impl = harnesshost::{prepare_harness_spawn, launch_harness_brokered_in} + cli cmd_endpoint_run; unit = harnesshost template-fill/fail-closed/mint; int = spt_hosted_bringup_then_cross_node_attach_drives_the_pty (bringup spawns into the broker PTY, cross-node attach drives it). cwd/project deferred to REQ-HOST-RUN-2 (W1 ships broker-inherited cwd)\n 490|[[requirements]]\n\n# crates/\n\n## spt-daemon/\n\n### src/\n#### dispatch.rs\n 173|/// distinguish in-flight vs terminal vs retryable, and a transient failure\n*174|/// requeues bounded (backoff-shaped, never a hot loop).\n 175|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 176|enum ClaimState {\n 177|    /// A worker owns the stream right now (`attempts` = prior failures).\n...\n 189|\n*190|/// Backoff base for a released claim (doubles per failed attempt).\n 191|const DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n 192|\n*193|/// The backoff before retry number `attempts` (1-based) runs.\n*194|fn retry_backoff(attempts: u32) -> Duration {\n 195|    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n 196|}\n 197|\n...\n 211|/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n*212|/// hot-loops. A failure requeues with backoff until the bounded budget is\n 213|/// spent, then goes terminal.\n 214|// [impl->REQ-DISPATCH-CLAIM-RETRY]\n 215|fn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\n...\n 224|                    attempts,\n*225|                    next_at: now + retry_backoff(attempts),\n 226|                }\n 227|            }\n 228|        }\n...\n 318|            // Terminal rows never re-enter; a released (Retry) claim re-enters\n*319|            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n 320|            if s.initiated_locally || s.next_seq == 0 {\n 321|                continue;\n 322|            }\n...\n 347|                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n*348|                    // an older broker without the verb answers error (KH-2.3)\n 349|                    // and the row retires when the broker cycles.\n 350|                    if let Ok(mut retire_brain) = connect(&name) {\n 351|                        let _ = retire_brain.net_stream_retire(stream_id);\n...\n 390|/// it survives any ring roll — and fall back to the ring peek only when the\n*391|/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n 392|/// (terminal); `Err` = transient (the claim requeues bounded).\n 393|fn first_line(\n 394|    broker_name: &str,\n...\n 411|        }\n*412|        // Older broker (unknown verb answers error) or the row vanished —\n 413|        // fall through to the ring peek, the pre-ADR-0038 path.\n 414|        Err(_) => {}\n 415|    }\n...\n 419|/// Peek the stream's first retained NDJSON line on a throwaway brain\n*420|/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n 421|/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n 422|/// retained chunk) so a rolled ring replays instead of tripping the brain's\n 423|/// gap check.\n...\n 960|    // [unit->REQ-DISPATCH-CLAIM-RETRY] the claim lifecycle, at its pure seam:\n*961|    // a TRANSIENT failure releases the claim and requeues it after a backoff;\n 962|    // TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter\n 963|    // — a persistently-failing stream retries exactly its bounded budget and\n 964|    // stops (the no-hot-loop ruling), where the pre-fix one-shot HashSet\n...\n 975|\n*976|        // Failure #1 releases with backoff — NOT claimable before next_at,\n 977|        // claimable at/after it, carrying the attempt count.\n 978|        let failed = DispatchOutcome::Failed(\"io\".into());\n 979|        let s1 = outcome_transition(&failed, 0, now);\n...\n 982|        };\n*983|        assert_eq!(next_at, now + retry_backoff(1));\n*984|        assert_eq!(should_claim(Some(&s1), now), None, \"backoff holds the claim\");\n 985|        assert_eq!(should_claim(Some(&s1), next_at), Some(1), \"due → reclaimable\");\n 986|\n*987|        // Backoff doubles per attempt.\n*988|        assert_eq!(retry_backoff(2), retry_backoff(1) * 2);\n 989|\n 990|        // The budget is BOUNDED: the MAX'th failure goes terminal, silent\n 991|        // forever after (no hot loop under a persistently-failing stream).\n#### brain.rs\n 198|/// cross-platform, and on Windows named pipes interprocess 2.4.2 offers NEITHER\n*199|/// portable read timeout (`set_recv_timeout` → `no_timeouts()`; `set_nonblocking`\n 200|/// → deprecated `PIPE_NOWAIT`, which corrupts mid-stream — proven by the mesh\n 201|/// E2E). So the pump carrier instead **splits** the stream: a dedicated reader\n 202|/// thread does blocking [`read_frame`] on the [`RecvHalf`] and forwards each\n 203|/// framed result down a channel; the main thread writes on the [`SendHalf`] and\n*204|/// reads with [`Receiver::recv_timeout`]. Blocking reads work on both OSes; the\n 205|/// channel supplies the deadline. On a timeout the pump bubbles out to a\n 206|/// supervised restart (a fresh [`Brain`] → fresh split → fresh reader thread);\n 207|/// the old reader, parked in `read_frame`, is abandoned (KH 7.6 leak watch — the\n...\n 296|    /// into the reader-thread form so the deadline can be honored over a channel\n*297|    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\n 298|    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\n 299|    /// single-threaded pump, where a black-holed peer would wedge the whole loop\n 300|    /// (KNOWN-HAZARDS — a blocked brain-IPC read must never wedge the pump). The\n...\n 1130|    /// the broker's compile-time constant, NEVER inferred from disk. Returns\n*1131|    /// `None` when the broker is too old to answer: an older broker replies\n 1132|    /// `unknown command kind` (the router forward-compat default), which is itself\n*1133|    /// the signal that the running image predates this build. Matched on raw KIND\n 1134|    /// (like [`Brain::adapter_apply`]) so the `unknown` error is caught here as\n 1135|    /// `None`, not propagated as a hard error.\n 1136|    // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\n...\n 1146|                }\n*1147|                // An older broker doesn't know this kind → `unknown command kind`\n*1148|                // error. That IS the signal: the broker predates the self-report.\n 1149|                KIND_ERROR => return Ok(None),\n 1150|                _ => continue,\n 1151|            }\n...\n 1158|    /// brain conn since boot, and when the most recent fired. `None` when the broker\n*1159|    /// is too old to answer (an older broker replies `unknown command kind`), read as\n 1160|    /// \"not reported\" on `daemon status` — same forward-compat contract as\n 1161|    /// [`broker_image_version`](Self::broker_image_version).\n 1162|    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n...\n 1448|    /// future dispatcher generation rediscovers and replays the stream\n*1449|    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\n*1450|    /// answers `error` (KH-2.3 N-1 window — retirement then waits for the\n 1451|    /// broker to cycle onto the new binary).\n 1452|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1453|    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n...\n 1475|    /// stream but no opener is pinned (no newline yet / capture gave up).\n*1476|    /// `Err` = no such stream, or an older broker without the verb (KH-2.3 —\n 1477|    /// the dispatcher falls back to the ring peek).\n 1478|    // [impl->REQ-STREAM-OPENER-DURABLE]\n 1479|    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n...\n 1511|    /// bare broker with no supervisor, e.g. a test broker — the verb is accepted\n*1512|    /// but nothing respawns). An older broker that predates the verb answers with\n 1513|    /// an error (KH-2.3 forward-compat: surfaced, never a silent no-op of a\n 1514|    /// swapped binary). Readiness-gated promotion / auto-rollback is D6.\n 1515|    // [impl->REQ-UPD-3]\n...\n 1738|    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns\n*1739|    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]\n 1740|    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]\n 1741|    ///   — the poison signal the pump escalates to a supervised restart (NEVER a\n 1742|    ///   per-peer retry). A `Disconnected` channel means the reader thread exited\n...\n 1752|                    // cannot reset it (the per-read-timeout wedge the ruling\n*1753|                    // forbids). A non-positive remaining → recv_timeout(ZERO),\n 1754|                    // which still drains an already-queued frame before timing out.\n 1755|                    let remaining = dl.saturating_duration_since(Instant::now());\n*1756|                    match frames.recv_timeout(remaining) {\n 1757|                        Ok(framed) => framed,\n 1758|                        Err(RecvTimeoutError::Timeout) => Err(io::Error::new(\n 1759|                            io::ErrorKind::TimedOut,\n...\n 1842|        // Pump mode, 30s carrier: reply-read capped to the 10s budget.\n*1843|        let d = peer_reply_deadline(Some(Duration::from_secs(30)), now).expect(\"pump = Some\");\n 1844|        assert_eq!(d, now + PEER_REPLY_READ_BUDGET, \"capped at the 10s budget, not 30s\");\n*1845|        assert!(d < now + Duration::from_secs(30), \"strictly before the carrier deadline\");\n 1846|        // A carrier timeout SHORTER than the budget wins the min (never exceed it).\n 1847|        let short = peer_reply_deadline(Some(Duration::from_secs(3)), now).unwrap();\n 1848|        assert_eq!(short, now + Duration::from_secs(3), \"min honors a shorter carrier bound\");\n#### broker.rs\n 140|/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n*141|/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n 142|/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n 143|/// never trips the deadline.\n 144|const CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n...\n 153|/// untouched.\n*154|const CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n 155|\n 156|/// How long a controller's writer thread may sit BLOCKED inside a single socket\n 157|/// write — the brain subscriber suspended / black-holed — before a take/reattach\n...\n 159|/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n*160|/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n 161|/// protects the fan-out against ONE per-client conn falling behind and is\n 162|/// output-driven (the drain observes a Full channel); THIS bound is measured\n 163|/// full-with-**zero writer progress** and covers the case the output-driven path\n...\n 171|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n*172|const BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n 173|\n*174|/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n*175|/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n 176|/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n 177|/// the slot) is provable in ~1 s instead of 15 (mirrors the\n 178|/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n...\n 180|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n*181|fn brain_write_deadline() -> Duration {\n*182|    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n 183|        Ok(ms) => ms\n 184|            .parse::<u64>()\n 185|            .map(Duration::from_millis)\n*186|            .unwrap_or(BRAIN_WRITE_DEADLINE),\n*187|        Err(_) => BRAIN_WRITE_DEADLINE,\n 188|    }\n 189|}\n 190|\n...\n 285|/// buggy translation binary can therefore stall operator input for at most this\n*286|/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n 287|const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n 288|\n 289|/// The effective commit deadline. Production is the const above; the int gate\n...\n 337|/// budget shape: consecutive-failures bounded, healthy-run resets). Mirrors the\n*338|/// psyche crash-loop backoff's bounded give-up.\n 339|const TRANSLATE_RESPAWN_BUDGET: u32 = 3;\n 340|\n 341|/// The effective respawn budget. Production is the const above; the int gate tunes\n...\n 414|\n*415|/// Chunk size for the paced text write (Layer 3, the ruling's named fallback where\n 416|/// bracketed-paste is not declared): a large payload is enqueued in bounded chunks\n 417|/// rather than one write, so no single huge write races the terminal's coalescer.\n 418|/// Layered UNDER the settle-gate (the reader is already confirmed live before the\n...\n 492|/// advances `delivered_through` on each successful write, and it is never evicted\n*493|/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\n 494|struct ControllerSink {\n 495|    /// The bounded live-frame queue to the controller's writer thread.\n 496|    tx: SyncSender<CtrlMsg>,\n...\n 505|    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n*506|    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n 507|    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n 508|    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n 509|    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n...\n 515|    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n*516|    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n 517|    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n 518|    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n 519|    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n...\n 603|/// The post-write resume cursor (pure — the D4-1 unit kernel, mirroring\n*604|/// `brainproc::consumer_gate`/`next_backoff`): advance to `seq + 1` **only** on a\n 605|/// successful write, and **monotonically** (never below `current`). A failed\n 606|/// write (`write_ok == false`) leaves the cursor untouched, and a re-send of an\n 607|/// already-delivered `seq` (an `attach(sub, 0)` rewind, `seq + 1 <= current`)\n...\n 723|    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n*724|    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n 725|    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n 726|    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n 727|    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n...\n 729|    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n*730|    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n 731|    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n 732|    /// (bounded wedge preserved). The writer still does the blocking socket write +\n 733|    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n...\n 837|                Err(TrySendError::Full(_)) => {\n*838|                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n 839|                        Some(epoch) // stalled past the deadline → bounded detach\n 840|                    } else {\n 841|                        None // drop this chunk; controller falls behind the ring\n\n### tests/\n#### redispatch.rs\n 247|    let (mut cur1, mut view1) = (0u64, Vec::new());\n*248|    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\n 249|    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n 250|    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1\");\n 251|\n...\n 253|    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n*254|    let end = Instant::now() + Duration::from_secs(20);\n 255|    while peer_stream_ids(&mut probe).contains(&s1_a) {\n 256|        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n 257|        thread::sleep(Duration::from_millis(50));\n...\n 265|    let (mut cur2, mut view2) = (0u64, Vec::new());\n*266|    let eof_pre = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-ONE\", Duration::from_secs(20));\n 267|    assert!(!eof_pre, \"no EOF while gen1 serves the live viewport\");\n 268|    let s2_a = *peer_stream_ids(&mut probe).first().expect(\"s2 row on A\");\n 269|\n...\n 278|    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\n*279|    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\n 280|    assert!(\n 281|        !eof_post,\n 282|        \"the current controller's stream saw an EOF across the restart — a replayed \\\n...\n 344|    let (mut cur1, mut view1) = (0u64, Vec::new());\n*345|    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\n 346|    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n 347|\n 348|    // The brain-down-detach window: kill gen1 FIRST (no worker can report\n...\n 389|            format!(\"CUR-{round}\").as_bytes(),\n*390|            Duration::from_secs(20),\n 391|        );\n 392|        assert!(!eof, \"round {round}: the current controller's stream saw an EOF — a stale replay cleared it\");\n 393|        thread::sleep(Duration::from_millis(700));\n...\n 397|    {\n*398|        let end = Instant::now() + Duration::from_secs(15);\n 399|        while peer_stream_ids(&mut probe).contains(&s1_a) {\n 400|            assert!(Instant::now() < end, \"the finished-unretired row never drained from the table\");\n 401|            thread::sleep(Duration::from_millis(50));\n...\n 522|    let (mut cur, mut view) = (0u64, Vec::new());\n*523|    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-A\", Duration::from_secs(20));\n 524|    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n 525|\n 526|    // Roll the opener out of the (8-chunk) ring: enough spaced input records\n...\n 532|    }\n*533|    let end = Instant::now() + Duration::from_secs(20);\n 534|    loop {\n 535|        let rolled = probe\n 536|            .net_streams()\n...\n 558|    send_attach_input(&mut operator, s1, b\"MARK-B\\r\", 100).expect(\"input post\");\n*559|    let saw_eof = render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-B\", Duration::from_secs(30));\n 560|    assert!(!saw_eof, \"the resumed stream must not be torn down\");\n 561|    let gen2_log = gen2.stderr_log();\n 562|    assert!(\n...\n 573|// generation's env): the FIRST worker attempt fails before setup, the claim\n*574|// requeues (bounded backoff), and the retry serves the stream — the operator\n 575|// still gets the viewport, exactly one controller. Pre-fix the one-shot\n 576|// claimed-set burn abandoned the stream permanently on that first failure.\n 577|#[test]\n...\n 607|    let (mut cur, mut view) = (0u64, Vec::new());\n*608|    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"RETRY-MARK\", Duration::from_secs(30));\n 609|\n 610|    // The injection actually fired (this is what distinguishes \"recovered\n 611|    // through retry\" from \"nothing ever failed\").\n\n## spt/tests/\n### daemon_refresh_e2e.rs\n 41|    });\n*42|    rx.recv_timeout(deadline)\n 43|        .expect(\"captured spt call must complete within its deadline\")\n 44|        .expect(\"run spt\")\n 45|}\n...\n 140|    let (brain_pid_before, gen_before) =\n*141|        match wait_ready_not(&ready_path, None, Duration::from_secs(30)) {\n 142|            Some(r) => r,\n 143|            None => {\n 144|                let _ = broker.kill();\n...\n 188|            .env(\"SPT_HOME\", home.path());\n*189|        output_bounded(cmd, Duration::from_secs(30))\n 190|    };\n 191|    let refresh_stdout = String::from_utf8_lossy(&refresh.stdout).to_string();\n 192|    let refresh_stderr = String::from_utf8_lossy(&refresh.stderr).to_string();\n...\n 195|    //    generation (no binary swapped anywhere). ──\n*196|    let cycled = wait_ready_not(&ready_path, Some(brain_pid_before), Duration::from_secs(30));\n 197|\n 198|    // The broker process must be the SAME OS process (never restarted).\n 199|    let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);\n...\n 239|            .env(\"SPT_HOME\", home.path());\n*240|        output_bounded(cmd, Duration::from_secs(20))\n 241|    };\n 242|    if let Some((pid, _)) = cycled {\n 243|        kill_pid(pid);\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"crates/spt-daemon/src/dispatch.rs, crates/spt-daemon/src/brain.rs, crates/spt-daemon/src/broker.rs, crates/spt-daemon/tests/redispatch.rs, crates/spt/tests/daemon_refresh_e2e.rs, docs/adr/0038-broker-stream-lifecycle-truth.md, traceable-reqs.toml","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":91,"fileCount":6,"files":["crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt/tests/daemon_refresh_e2e.rs","traceable-reqs.toml"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":17},{"path":"crates/spt-daemon/src/brain.rs","count":17},{"path":"crates/spt-daemon/src/broker.rs","count":20},{"path":"crates/spt-daemon/tests/redispatch.rs","count":12},{"path":"crates/spt/tests/daemon_refresh_e2e.rs","count":5},{"path":"traceable-reqs.toml","count":20}],"truncated":true,"perFileLimitReached":20,"displayContent":"# traceable-reqs.toml\n  33│id = \"REQ-ARCH-3\"\n *34│title = \"Wire-protocol version independent of crate semver, N-1 compat window\"\n  35│required_stages = [\"impl\", \"unit\"]  # activated M0: spt-proto wire version + compat window (T6)\n  36│[[requirements]]\n  37│id = \"REQ-ARCH-4\"\n    │...\n  42│[[requirements]]\n *43│id = \"REQ-DAEMON-1\"\n  44│title = \"One per-machine spt-daemon owning all per-machine state\"\n  45│required_stages = [\"impl\", \"unit\", \"int\"]  # int activated M3b-B9: the daemon E2E (spawn→Psyche-loop→commune→brain-restart-survives→graceful-signoff) drives the consolidated daemon-hosted lifecycle through the real IPC\n  46│[[requirements]]\n    │...\n  63│title = \"spt-store::BranchStore (git branch as versioned KV; commit=checkpoint/tip=resume, atomic multi-key, merge-native sync) is the substrate for coarse/durable/audited state (context, registry snapshot+distribution, daemon checkpoint); hot paths (B5 fsync journal) + indexed queries (SQLite spool) excluded (ADR-0011)\"\n *64│required_stages = [\"impl\", \"unit\"]  # activated M4-D6a: spt_store::branchstore::BranchStore — bare seed repo + linked worktrees (relative-path config, repair fallback), ensure_branch = parentless empty-tree seed (independent KV roots; sync joins with --allow-unrelated-histories at first contact), commit_in_worktree = ONE commit per write incl. atomic multi-key (no-op mints no empty checkpoint), tip/read_at_tip = the recovery read. Git-CLI-backed per ADR-0013 (bounded runner gitrun.rs, hazard 5.3; ident...\n  65│\n  66│# ───────────────────────────── Harness contract — manifest seams ────────────\n  67│[[requirements]]\n    │...\n  88│id = \"REQ-MANIFEST-6\"\n *89│title = \"Cross-adapter fallback target addressing (M12-W3-T3.2): a cross-adapter fallback target is addressed as `<adapter>:<profile>` (not just a bare adapter_name), resolved through the one composite-addressing resolver (registry::resolve_option) at every adapter-option read site so a fallback may select a shipped/local profile (e.g. a `ccs` profile). CONTEXT.md §cross-adapter-fallback reconciled (\\\"ccs is a profile; cross-adapter fallback may target <adapter>:<profile>\\\"). Contract-only this mileston...\n  90│required_stages = [\"doc\", \"unit\"]  # activated M12-W3 (contract-only: no int)\n  91│[[requirements]]\n  92│id = \"REQ-MANIFEST-7\"\n *93│title = \"Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the `spt endpoint run` picker bakes into the generated `<basename>-<id>` launcher shortcut (REQ-RUN-SHORTCUT). Absent ⇒ the harness-agnostic default `spt` (→ `spt-<id>`); an adapter sets it to brand its shortcuts (claude-spt → `cc` → `cc-<id>`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it f...\n  94│required_stages = [\"doc\", \"impl\", \"unit\"]  # activated M12-W2 follow-on\n  95│[[requirements]]\n  96│id = \"REQ-MANIFEST-8\"\n *97│title = \"[adapter] host_binaries declares the harness executable basenames a kind=\\\"harness\\\" adapter hosts agents inside (e.g. host_binaries = [\\\"claude\\\"]); bind-time pid→exe-basename match (case-insensitive, .exe-stripped) over the seed's parent_pid selects the candidate adapter set; zero matches → a friendly error naming the binary + the --adapter escape hatch. Additive + N-1-safe: optional Vec<String>, #[serde(default, skip_serializing_if = \\\"Vec::is_empty\\\")] (omitted-serialized like shortcut_b...\n  98│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # activated v0.9.0 W2: doc = CONTEXT.md §179 host_binaries candidate rule + docs/MANIFEST.md + docs-site harness-contract host_binaries field. impl = the `[adapter] host_binaries` field (manifest.rs, serde-default omit-empty) + spt_store::proc::exe_basename (Win QueryFullProcessImageNameW / Linux /proc/pid/exe) + the resolver's host_binaries candidate filter (spt-runtime resolve). unit = host_binaries_optional_and_n1_safe (manifest) + exe_basename_resolv...\n  99│[[requirements]]\n 100│id = \"REQ-SEAM-SPAWN\"\n    │...\n 207│id = \"REQ-EP-8\"\n*208│title = \"AlwaysOnEndpoint: a resident, addressable, mindless endpoint whose adapter binary the daemon supervises continuously — register-triggered by an adapter-option's `[always-on]` manifest section, one supervised binary per `<adapter>[:profile]`, running independent of agent liveness. It self-manages its `#`-addressed channel endpoints via the existing `api bind` (one connection fronts many). The SECOND class of spt-core-boot-launched third-party binary (after the shell wake-watcher); supervision r...\n 209│required_stages = []  # rule 5: design ratified 2026-06-21 (grill-with-docs, ADR-0023); UNBUILT — activate when the milestone delivering [always-on] supervision lands. Reuses shellwake.rs scaffolding (REQ-SHELL-2) minus the offline flip; first consumer is the downstream spt-discord adapter (own repo).\n 210│[[requirements]]\n 211│id = \"REQ-EP-9\"\n    │...\n 222│title = \"Per-node files, synced Psyche mind\"\n*223│required_stages = [\"impl\", \"unit\"]  # activated M4-D6a: the mind now lives in the tracked/ two-tier store (spt-store::contextstore over BranchStore), physically separate from never-synced node-local perches/ — spt_live::ingest::route_slices routes commune drops' <live-context>/<project-context> slices into a-<id>/p-<project_id> branch files (untagged body → live, the parser fallback; precedence-guarded 6.5; checkpoint commit per written tier); signoff::write_resume_commune stamps Self provenance per ...\n 224│[[requirements]]\n 225│id = \"REQ-INST-3\"\n 226│title = \"Dormant (warm) / suspended (cold) resting states\"\n    │...\n 238│title = \"Deferred messages not delivered to dormant/suspended instances\"\n*239│required_stages = [\"impl\", \"unit\", \"int\"]  # activated M5-D5a/b: the gate is state-keyed at the hook-channel consumer sites — resting::deferred_held reads the owner's durable rest record at drain time; \"held\" = (deferred=1) ∧ (Dormant|Suspended); poll_drain + cmd_worker_poll gated (the KH 1.4 \"all sites agree\" sweep — shellchan peek ungated: no deferred writer targets shell perches; ring's ephemeral perch recordless by construction = legacy fallback). Release is by construction at the wake edge: th...\n 240│[[requirements]]\n 241│id = \"REQ-INST-7\"\n 242│title = \"Subnet registry + bare-id resolution policy\"\n    │...\n 291│id = \"REQ-MSG-1\"\n*292│title = \"Local message delivery: TCP-first to a registered address, spool fallback when offline; id->address via registry (stale-clean first); reply routing (__REPLY_TO__)\"\n 293│required_stages = [\"impl\", \"unit\", \"int\"]  # activated M1 T1 (impl+unit); int added T9: killer-quickstart cross-perch E2E\n 294│[[requirements]]\n 295│id = \"REQ-MSG-2\"\n    │...\n 311│id = \"REQ-MSG-5\"\n*312│title = \"user-msg envelope kind + daemon identity gate: a Gateway endpoint / the local user's CLI author user-msg (the user's authority); agent-family senders re-stamped to plain msg; identity-gated never payload-trusted (KH 7.3/7.5); wire-additive (N-1 receivers tolerate the new type)\"\n*313│required_stages = [\"doc\", \"impl\", \"unit\"]  # activated 2026-06-13: M9-T4 (CONTEXT.md §Gateway user-msg; spt-proto EVENT_TYPE_USER_MSG + compose_user_msg_event + the MsgOrigin identity gate / gate_user_msg_type truth table incl. spoof rows; wire-additive N-1 tolerance fixture). Daemon origination/WAN-ingress wiring + render rides the same activation.\n 314│[[requirements]]\n 315│id = \"REQ-MSG-6\"\n*316│title = \"cross-node Gateway user-msg honored via advertised endpoint_type: a user-msg from a Gateway-typed origin survives the receive_wan funnel as user-msg (vs the fail-closed re-stamp), keyed on the QUIC-handshake-proven origin node (never wire `from`). Trust boundary = subnet membership (operator-ratified 2026-06-13); no defense against an in-subnet member forging the type. Instance.endpoint_type is an additive serde-default field extending REQ-INST-7's data model. Absent/unknown type → re-stamp (N...\n 317│required_stages = [\"doc\", \"impl\", \"unit\"]  # activated 2026-06-13: M9 cross-node Gateway WAN. doc = CONTEXT.md §Gateway posture rewrite (commit 30fea7f); impl = Instance.endpoint_type field (T1) + advertise populate (T2) + receive_wan origin resolve (T3); unit = the origin matrix in wan.rs + advertise-populate assertion (T4). doyle gate PASS 2026-06-13 (PR #9). INT DEFERRED, REQUIRED-BEFORE-LECTURN (tracked gate condition): the T5 twohost HONORED-path int (Gateway advert on A → user-msg honored at B; ...\n 318│\n 319│# ───────────────────────────── Networking / pairing ─────────────────────────\n    │...\n 334│title = \"Cross-node Psyche sync over P2P replaces gh-repo-sync\"\n*335│required_stages = [\"impl\", \"unit\"]  # activated M4-D6c: git-native bundle sync (ADR-0013). D6c-1 store core: BranchStore bundle plumbing (create_bundle ^have incremental w/ unknown-have full fallback + up-to-date skip; fetch_bundle quarantines tips under refs/spt-sync/ — incoming can never clobber refs/heads before the driver rules; merge_commit_in_worktree two-parent join, FF for all-accepted propagation) + spt_store::syncmerge::apply_fetched — per-file vector verdicts (dominate→accept verbatim/do...\n 336│[[requirements]]\n 337│id = \"REQ-PAIR-1\"\n 338│title = \"TOTP-seeded SPAKE2 pairing\"\n    │...\n 359│title = \"Elevation-gated per-subnet code fetch (UAC/root or elevated agent; else authenticator app)\"\n*360│required_stages = [\"impl\", \"unit\"]  # activated M4-D2g: elevation.rs probes OS privilege (Linux libc::geteuid()==0; Windows GetTokenInformation(TokenElevation) via windows-sys; cfg-split) — only the binary pulls OS deps. gate_for: Elevated => Show, NotElevated|Unknown => Fallback (fail safe, never leak the code unconfirmed). Refused fetch leaks no code, persists no mint, distinct exit 3, and the fallback names the authenticator app (seed provisioned there at pairing). unit = gate decision both ways + U...\n 361│[[requirements]]\n 362│id = \"REQ-PAIR-7\"\n 363│title = \"Subnet icon (inline image metadata, GUI-only consumer)\"\n    │...\n 384│title = \"spt how-to <topic>: in-binary task-oriented agent instructions (anti-drift; quickstart prompts point agents at it)\"\n*385│required_stages = [\"impl\", \"unit\", \"int\"]  # activated M7 D1 (2026-06-05); int added M7 D5: quickstart_e2e asserts the how-to texts name the commands the published guide's prompt blocks rely on. M7 D1/D4 (plan decision 12). Visible command, Agent-commands help group. Topics v1: `ready` (background-task guidance, --once fallback loop, reply mechanics) + `send` (send/ring, reply via plain send to the structural `from`, SENT vs QUEUED); bare `how-to` lists topics. Single source = the binary — docs-site ne...\n 386│\n 387│# ── Endpoint access control (ADR-0009, 2026-06-02; activate M4 node-tier) ──\n 388│[[requirements]]\n    │...\n 432│id = \"REQ-UPD-8\"\n*433│title = \"Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3....\n*434│required_stages = [\"impl\", \"unit\"]  # activate v0.3.2. impl = fetch set-staging (cli) + apply_staged single platform-guard (applyhost) + ReleaseCache platform stamp (relcache) + product_version metadata (release.rs) + xtask release-publish emits/uploads update-set.json stamped with product_version. unit = (a) fetch update-set.json + per-asset URL/triple derivation; (b) apply REFUSES a platform-mismatched / unstamped single (the regression that bricked hfenduleam) + a matching-stamp single applies; (c) th...\n 435│[[requirements]]\n 436│id = \"REQ-UPD-9\"\n 437│title = \"`gh_release` adapter [update] avenue (optional signing): an adapter declares `[update] avenue = \\\"gh_release\\\", repo = \\\"user/repo\\\"` (+ optional `asset`, default `adapter.spt`; + optional Ed25519 `signing_key`); spt-core's ripple compares the repo's LATEST GitHub release version against the installed adapter version and, when newer, auto-updates by fetching the release `.spt` archive (the REQ-INSTALL-9 `--release` fetch primitive) → verifies the `.spt` against `signing_key` if declared, else ...\n    │...\n 462│id = \"REQ-TERM-5\"\n*463│title = \"Adapter-declared digest extractor seam: a `[digest]` manifest section declaring an imperative extractor (native harness log -> the {role,text,tool,ts} contract; defaults to the [history] source files with an own-source escape hatch), `api digest-entry` push fallback, register-time validation of the section, adapter-declared presentation defaults (window depth, arg-truncation, sprint-collapse) that any consumer may override, and a `spt adapter digest-proof` author tool plus runtime skip-diagnosti...\n 464│# End-state target [doc, impl, unit, int]; activated per-wave as evidence lands\n 465│# (rule 2: traceable-reqs check EXIT=0 every commit — stages match evidence, never\n 466│# pre-activated). doc landed with the MANIFEST [digest] section; int landed Wave 4 E2E.\n    │...\n 486│[[requirements]]\n*487│id = \"REQ-HOST-RUN-1\"\n 488│title = \"spt-hosted harness bringup: `spt endpoint run` spawns an adapter's `[session.self]` command template into a broker-held PTY (the spawn-session seam, brain.rs spawn_session_pid — same broker path shellhost.rs launch_shell_brokered_in uses for shells, now for kind=\\\"harness\\\" self-role), registers the perch under the given endpoint id, returns the id. Reverses today's harness-hosted-only launch (external launcher → `api bind`). Non-interactive flag set (--adapter <a[:profile]> --id <id> --crea...\n 489│required_stages = [\"impl\", \"unit\", \"int\"]  # activated M12-W1: impl = harnesshost::{prepare_harness_spawn, launch_harness_brokered_in} + cli cmd_endpoint_run; unit = harnesshost template-fill/fail-closed/mint; int = spt_hosted_bringup_then_cross_node_attach_drives_the_pty (bringup spawns into the broker PTY, cross-node attach drives it). cwd/project deferred to REQ-HOST-RUN-2 (W1 ships broker-inherited cwd)\n 490│[[requirements]]\n\n# crates/\n\n## spt-daemon/\n\n### src/\n#### dispatch.rs\n 173│/// distinguish in-flight vs terminal vs retryable, and a transient failure\n*174│/// requeues bounded (backoff-shaped, never a hot loop).\n 175│#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 176│enum ClaimState {\n 177│    /// A worker owns the stream right now (`attempts` = prior failures).\n    │...\n 189│\n*190│/// Backoff base for a released claim (doubles per failed attempt).\n 191│const DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n 192│\n*193│/// The backoff before retry number `attempts` (1-based) runs.\n*194│fn retry_backoff(attempts: u32) -> Duration {\n 195│    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n 196│}\n 197│\n    │...\n 211│/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n*212│/// hot-loops. A failure requeues with backoff until the bounded budget is\n 213│/// spent, then goes terminal.\n 214│// [impl->REQ-DISPATCH-CLAIM-RETRY]\n 215│fn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\n    │...\n 224│                    attempts,\n*225│                    next_at: now + retry_backoff(attempts),\n 226│                }\n 227│            }\n 228│        }\n    │...\n 318│            // Terminal rows never re-enter; a released (Retry) claim re-enters\n*319│            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n 320│            if s.initiated_locally || s.next_seq == 0 {\n 321│                continue;\n 322│            }\n    │...\n 347│                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n*348│                    // an older broker without the verb answers error (KH-2.3)\n 349│                    // and the row retires when the broker cycles.\n 350│                    if let Ok(mut retire_brain) = connect(&name) {\n 351│                        let _ = retire_brain.net_stream_retire(stream_id);\n    │...\n 390│/// it survives any ring roll — and fall back to the ring peek only when the\n*391│/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n 392│/// (terminal); `Err` = transient (the claim requeues bounded).\n 393│fn first_line(\n 394│    broker_name: &str,\n    │...\n 411│        }\n*412│        // Older broker (unknown verb answers error) or the row vanished —\n 413│        // fall through to the ring peek, the pre-ADR-0038 path.\n 414│        Err(_) => {}\n 415│    }\n    │...\n 419│/// Peek the stream's first retained NDJSON line on a throwaway brain\n*420│/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n 421│/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n 422│/// retained chunk) so a rolled ring replays instead of tripping the brain's\n 423│/// gap check.\n    │...\n 960│    // [unit->REQ-DISPATCH-CLAIM-RETRY] the claim lifecycle, at its pure seam:\n*961│    // a TRANSIENT failure releases the claim and requeues it after a backoff;\n 962│    // TERMINAL outcomes (served, unclassifiable, budget spent) never re-enter\n 963│    // — a persistently-failing stream retries exactly its bounded budget and\n 964│    // stops (the no-hot-loop ruling), where the pre-fix one-shot HashSet\n    │...\n 975│\n*976│        // Failure #1 releases with backoff — NOT claimable before next_at,\n 977│        // claimable at/after it, carrying the attempt count.\n 978│        let failed = DispatchOutcome::Failed(\"io\".into());\n 979│        let s1 = outcome_transition(&failed, 0, now);\n    │...\n 982│        };\n*983│        assert_eq!(next_at, now + retry_backoff(1));\n*984│        assert_eq!(should_claim(Some(&s1), now), None, \"backoff holds the claim\");\n 985│        assert_eq!(should_claim(Some(&s1), next_at), Some(1), \"due → reclaimable\");\n 986│\n*987│        // Backoff doubles per attempt.\n*988│        assert_eq!(retry_backoff(2), retry_backoff(1) * 2);\n 989│\n 990│        // The budget is BOUNDED: the MAX'th failure goes terminal, silent\n 991│        // forever after (no hot loop under a persistently-failing stream).\n#### brain.rs\n  198│/// cross-platform, and on Windows named pipes interprocess 2.4.2 offers NEITHER\n *199│/// portable read timeout (`set_recv_timeout` → `no_timeouts()`; `set_nonblocking`\n  200│/// → deprecated `PIPE_NOWAIT`, which corrupts mid-stream — proven by the mesh\n  201│/// E2E). So the pump carrier instead **splits** the stream: a dedicated reader\n  202│/// thread does blocking [`read_frame`] on the [`RecvHalf`] and forwards each\n  203│/// framed result down a channel; the main thread writes on the [`SendHalf`] and\n *204│/// reads with [`Receiver::recv_timeout`]. Blocking reads work on both OSes; the\n  205│/// channel supplies the deadline. On a timeout the pump bubbles out to a\n  206│/// supervised restart (a fresh [`Brain`] → fresh split → fresh reader thread);\n  207│/// the old reader, parked in `read_frame`, is abandoned (KH 7.6 leak watch — the\n     │...\n  296│    /// into the reader-thread form so the deadline can be honored over a channel\n *297│    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\n  298│    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\n  299│    /// single-threaded pump, where a black-holed peer would wedge the whole loop\n  300│    /// (KNOWN-HAZARDS — a blocked brain-IPC read must never wedge the pump). The\n     │...\n 1130│    /// the broker's compile-time constant, NEVER inferred from disk. Returns\n*1131│    /// `None` when the broker is too old to answer: an older broker replies\n 1132│    /// `unknown command kind` (the router forward-compat default), which is itself\n*1133│    /// the signal that the running image predates this build. Matched on raw KIND\n 1134│    /// (like [`Brain::adapter_apply`]) so the `unknown` error is caught here as\n 1135│    /// `None`, not propagated as a hard error.\n 1136│    // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\n     │...\n 1146│                }\n*1147│                // An older broker doesn't know this kind → `unknown command kind`\n*1148│                // error. That IS the signal: the broker predates the self-report.\n 1149│                KIND_ERROR => return Ok(None),\n 1150│                _ => continue,\n 1151│            }\n     │...\n 1158│    /// brain conn since boot, and when the most recent fired. `None` when the broker\n*1159│    /// is too old to answer (an older broker replies `unknown command kind`), read as\n 1160│    /// \"not reported\" on `daemon status` — same forward-compat contract as\n 1161│    /// [`broker_image_version`](Self::broker_image_version).\n 1162│    // [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n     │...\n 1448│    /// future dispatcher generation rediscovers and replays the stream\n*1449│    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\n*1450│    /// answers `error` (KH-2.3 N-1 window — retirement then waits for the\n 1451│    /// broker to cycle onto the new binary).\n 1452│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1453│    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n     │...\n 1475│    /// stream but no opener is pinned (no newline yet / capture gave up).\n*1476│    /// `Err` = no such stream, or an older broker without the verb (KH-2.3 —\n 1477│    /// the dispatcher falls back to the ring peek).\n 1478│    // [impl->REQ-STREAM-OPENER-DURABLE]\n 1479│    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n     │...\n 1511│    /// bare broker with no supervisor, e.g. a test broker — the verb is accepted\n*1512│    /// but nothing respawns). An older broker that predates the verb answers with\n 1513│    /// an error (KH-2.3 forward-compat: surfaced, never a silent no-op of a\n 1514│    /// swapped binary). Readiness-gated promotion / auto-rollback is D6.\n 1515│    // [impl->REQ-UPD-3]\n     │...\n 1738│    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns\n*1739│    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]\n 1740│    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]\n 1741│    ///   — the poison signal the pump escalates to a supervised restart (NEVER a\n 1742│    ///   per-peer retry). A `Disconnected` channel means the reader thread exited\n     │...\n 1752│                    // cannot reset it (the per-read-timeout wedge the ruling\n*1753│                    // forbids). A non-positive remaining → recv_timeout(ZERO),\n 1754│                    // which still drains an already-queued frame before timing out.\n 1755│                    let remaining = dl.saturating_duration_since(Instant::now());\n*1756│                    match frames.recv_timeout(remaining) {\n 1757│                        Ok(framed) => framed,\n 1758│                        Err(RecvTimeoutError::Timeout) => Err(io::Error::new(\n 1759│                            io::ErrorKind::TimedOut,\n     │...\n 1842│        // Pump mode, 30s carrier: reply-read capped to the 10s budget.\n*1843│        let d = peer_reply_deadline(Some(Duration::from_secs(30)), now).expect(\"pump = Some\");\n 1844│        assert_eq!(d, now + PEER_REPLY_READ_BUDGET, \"capped at the 10s budget, not 30s\");\n*1845│        assert!(d < now + Duration::from_secs(30), \"strictly before the carrier deadline\");\n 1846│        // A carrier timeout SHORTER than the budget wins the min (never exceed it).\n 1847│        let short = peer_reply_deadline(Some(Duration::from_secs(3)), now).unwrap();\n 1848│        assert_eq!(short, now + Duration::from_secs(3), \"min honors a shorter carrier bound\");\n#### broker.rs\n 140│/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n*141│/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n 142│/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n 143│/// never trips the deadline.\n 144│const CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n    │...\n 153│/// untouched.\n*154│const CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n 155│\n 156│/// How long a controller's writer thread may sit BLOCKED inside a single socket\n 157│/// write — the brain subscriber suspended / black-holed — before a take/reattach\n    │...\n 159│/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n*160│/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n 161│/// protects the fan-out against ONE per-client conn falling behind and is\n 162│/// output-driven (the drain observes a Full channel); THIS bound is measured\n 163│/// full-with-**zero writer progress** and covers the case the output-driven path\n    │...\n 171│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n*172│const BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n 173│\n*174│/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n*175│/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n 176│/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n 177│/// the slot) is provable in ~1 s instead of 15 (mirrors the\n 178│/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n    │...\n 180│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n*181│fn brain_write_deadline() -> Duration {\n*182│    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n 183│        Ok(ms) => ms\n 184│            .parse::<u64>()\n 185│            .map(Duration::from_millis)\n*186│            .unwrap_or(BRAIN_WRITE_DEADLINE),\n*187│        Err(_) => BRAIN_WRITE_DEADLINE,\n 188│    }\n 189│}\n 190│\n    │...\n 285│/// buggy translation binary can therefore stall operator input for at most this\n*286│/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n 287│const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n 288│\n 289│/// The effective commit deadline. Production is the const above; the int gate\n    │...\n 337│/// budget shape: consecutive-failures bounded, healthy-run resets). Mirrors the\n*338│/// psyche crash-loop backoff's bounded give-up.\n 339│const TRANSLATE_RESPAWN_BUDGET: u32 = 3;\n 340│\n 341│/// The effective respawn budget. Production is the const above; the int gate tunes\n    │...\n 414│\n*415│/// Chunk size for the paced text write (Layer 3, the ruling's named fallback where\n 416│/// bracketed-paste is not declared): a large payload is enqueued in bounded chunks\n 417│/// rather than one write, so no single huge write races the terminal's coalescer.\n 418│/// Layered UNDER the settle-gate (the reader is already confirmed live before the\n    │...\n 492│/// advances `delivered_through` on each successful write, and it is never evicted\n*493│/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\n 494│struct ControllerSink {\n 495│    /// The bounded live-frame queue to the controller's writer thread.\n 496│    tx: SyncSender<CtrlMsg>,\n    │...\n 505│    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n*506│    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n 507│    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n 508│    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n 509│    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n    │...\n 515│    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n*516│    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n 517│    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n 518│    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n 519│    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n    │...\n 603│/// The post-write resume cursor (pure — the D4-1 unit kernel, mirroring\n*604│/// `brainproc::consumer_gate`/`next_backoff`): advance to `seq + 1` **only** on a\n 605│/// successful write, and **monotonically** (never below `current`). A failed\n 606│/// write (`write_ok == false`) leaves the cursor untouched, and a re-send of an\n 607│/// already-delivered `seq` (an `attach(sub, 0)` rewind, `seq + 1 <= current`)\n    │...\n 723│    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n*724│    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n 725│    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n 726│    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n 727│    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n    │...\n 729│    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n*730│    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n 731│    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n 732│    /// (bounded wedge preserved). The writer still does the blocking socket write +\n 733│    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n    │...\n 837│                Err(TrySendError::Full(_)) => {\n*838│                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n 839│                        Some(epoch) // stalled past the deadline → bounded detach\n 840│                    } else {\n 841│                        None // drop this chunk; controller falls behind the ring\n\n### tests/\n#### redispatch.rs\n 247│    let (mut cur1, mut view1) = (0u64, Vec::new());\n*248│    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\n 249│    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n 250│    operator.net_stream_send(s1, &[], None, true).expect(\"detach s1\");\n 251│\n    │...\n 253│    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n*254│    let end = Instant::now() + Duration::from_secs(20);\n 255│    while peer_stream_ids(&mut probe).contains(&s1_a) {\n 256│        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n 257│        thread::sleep(Duration::from_millis(50));\n    │...\n 265│    let (mut cur2, mut view2) = (0u64, Vec::new());\n*266│    let eof_pre = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-ONE\", Duration::from_secs(20));\n 267│    assert!(!eof_pre, \"no EOF while gen1 serves the live viewport\");\n 268│    let s2_a = *peer_stream_ids(&mut probe).first().expect(\"s2 row on A\");\n 269│\n    │...\n 278│    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\n*279│    let eof_post = render_until(&mut operator, s2, &mut dec2, &mut cur2, &mut view2, b\"CUR-TWO\", Duration::from_secs(30));\n 280│    assert!(\n 281│        !eof_post,\n 282│        \"the current controller's stream saw an EOF across the restart — a replayed \\\n    │...\n 344│    let (mut cur1, mut view1) = (0u64, Vec::new());\n*345│    render_until(&mut operator, s1, &mut dec1, &mut cur1, &mut view1, b\"HIST-MARK\", Duration::from_secs(20));\n 346│    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n 347│\n 348│    // The brain-down-detach window: kill gen1 FIRST (no worker can report\n    │...\n 389│            format!(\"CUR-{round}\").as_bytes(),\n*390│            Duration::from_secs(20),\n 391│        );\n 392│        assert!(!eof, \"round {round}: the current controller's stream saw an EOF — a stale replay cleared it\");\n 393│        thread::sleep(Duration::from_millis(700));\n    │...\n 397│    {\n*398│        let end = Instant::now() + Duration::from_secs(15);\n 399│        while peer_stream_ids(&mut probe).contains(&s1_a) {\n 400│            assert!(Instant::now() < end, \"the finished-unretired row never drained from the table\");\n 401│            thread::sleep(Duration::from_millis(50));\n    │...\n 522│    let (mut cur, mut view) = (0u64, Vec::new());\n*523│    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-A\", Duration::from_secs(20));\n 524│    let s1_a = *peer_stream_ids(&mut probe).first().expect(\"s1 row on A\");\n 525│\n 526│    // Roll the opener out of the (8-chunk) ring: enough spaced input records\n    │...\n 532│    }\n*533│    let end = Instant::now() + Duration::from_secs(20);\n 534│    loop {\n 535│        let rolled = probe\n 536│            .net_streams()\n    │...\n 558│    send_attach_input(&mut operator, s1, b\"MARK-B\\r\", 100).expect(\"input post\");\n*559│    let saw_eof = render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-B\", Duration::from_secs(30));\n 560│    assert!(!saw_eof, \"the resumed stream must not be torn down\");\n 561│    let gen2_log = gen2.stderr_log();\n 562│    assert!(\n    │...\n 573│// generation's env): the FIRST worker attempt fails before setup, the claim\n*574│// requeues (bounded backoff), and the retry serves the stream — the operator\n 575│// still gets the viewport, exactly one controller. Pre-fix the one-shot\n 576│// claimed-set burn abandoned the stream permanently on that first failure.\n 577│#[test]\n    │...\n 607│    let (mut cur, mut view) = (0u64, Vec::new());\n*608│    render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"RETRY-MARK\", Duration::from_secs(30));\n 609│\n 610│    // The injection actually fired (this is what distinguishes \"recovered\n 611│    // through retry\" from \"nothing ever failed\").\n\n## spt/tests/\n### daemon_refresh_e2e.rs\n  41│    });\n *42│    rx.recv_timeout(deadline)\n  43│        .expect(\"captured spt call must complete within its deadline\")\n  44│        .expect(\"run spt\")\n  45│}\n    │...\n 140│    let (brain_pid_before, gen_before) =\n*141│        match wait_ready_not(&ready_path, None, Duration::from_secs(30)) {\n 142│            Some(r) => r,\n 143│            None => {\n 144│                let _ = broker.kill();\n    │...\n 188│            .env(\"SPT_HOME\", home.path());\n*189│        output_bounded(cmd, Duration::from_secs(30))\n 190│    };\n 191│    let refresh_stdout = String::from_utf8_lossy(&refresh.stdout).to_string();\n 192│    let refresh_stderr = String::from_utf8_lossy(&refresh.stderr).to_string();\n    │...\n 195│    //    generation (no binary swapped anywhere). ──\n*196│    let cycled = wait_ready_not(&ready_path, Some(brain_pid_before), Duration::from_secs(30));\n 197│\n 198│    // The broker process must be the SAME OS process (never restarted).\n 199│    let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);\n    │...\n 239│            .env(\"SPT_HOME\", home.path());\n*240│        output_bounded(cmd, Duration::from_secs(20))\n 241│    };\n 242│    if let Some((pid, _)) = cycled {\n 243│        kill_pid(pid);","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784247388207}}
{"type":"message","id":"a16d346a","parentId":"e009f7ca","timestamp":"2026-07-17T00:16:51.600Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Clarifying release tag commit**\n\n**Analyzing cross-version broker compatibility issues**\n\n**Evaluating opener eviction and attach risks**"},{"type":"thinking","thinking":"**Analyzing fallback stall causes**\n\n**Investigating 20-30 second stall timing**"},{"type":"toolCall","id":"call_PJ6naFEsReuduwvkWEkeTLuP|fc_099a7567dadef391016a5974738b94819bb8c12b994a7f6cb8","name":"read","arguments":{"i":"Reading requirement release entries","path":"traceable-reqs.toml","selector":"2390-2452"}},{"type":"toolCall","id":"call_V7CGQmcMZLtwCpvrgrueHwEj|fc_099a7567dadef391016a5974738ba8819bbd32fc7731d33029","name":"read","arguments":{"i":"Reading lifecycle hazard timing","path":"docs/KNOWN-HAZARDS.md","selector":"620-662,676-716"}},{"type":"toolCall","id":"call_tFGSIrmOB3RnCSIBPv6ATvsu|fc_099a7567dadef391016a5974738bb4819b8fa8da148580d2ae","name":"grep","arguments":{"i":"Finding brain promotion timing","pattern":"TRIAL|trial.*Duration|READY|DRAIN|15s|15 s|30s|30 s|old_gen|promotion","path":"crates/spt-daemon/src/brainproc.rs; docs; traceable-reqs.toml","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_L4n1lExWX59Eih3xxGdfltrP|fc_099a7567dadef391016a5974738bb8819b9a1abc5ae98b4c47","name":"read","arguments":{"i":"Reading Redispatch test harness setup","path":"crates/spt-daemon/tests/redispatch.rs","selector":"1-200"}},{"type":"toolCall","id":"call_UJt6vJp4AJxM3tVfzomqqUh8|fc_099a7567dadef391016a5974738bc0819b8fb83efc65fb323d","name":"read","arguments":{"i":"Reading opener IPC behavior","path":"crates/spt-daemon/src/brain.rs","selector":"1438-1505"}},{"type":"toolCall","id":"call_9OtqiNkClcpbFHPdz1QYk7bI|fc_099a7567dadef391016a5974738bc4819b9699dbe56d0f1c23","name":"read","arguments":{"i":"Reading stream retirement implementation","path":"crates/spt-daemon/src/nethost.rs","selector":"120-282,535-558,1548-1605"}},{"type":"toolCall","id":"call_CCrSlQTRMzvqA3pEEy52vDd6|fc_099a7567dadef391016a5974738bcc819ba690ad2840d2fad8","name":"grep","arguments":{"i":"Checking mixed-image coverage","pattern":"older broker|old broker|N-1|mixed|v0\\.33|predates|unknown command|net_stream_opener|net_stream_retire","path":"crates/spt-daemon/tests; crates/spt/tests; crates/spt-daemon/src","selector":null,"case":false,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":23396,"output":1161,"cacheRead":81408,"cacheWrite":0,"totalTokens":105965,"reasoningTokens":767,"cost":{"input":0.11698000000000001,"output":0.03483,"cacheRead":0.040704,"cacheWrite":0,"total":0.192514}},"stopReason":"toolUse","timestamp":1784247388227,"responseId":"resp_099a7567dadef391016a59745c679c819bbb56bdecdcf1a23f","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a59745d5b0c819b81780a11212b0471","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXRoUQ8SfIcsZypcaJOjMarkEYHtN07T-u-Cgil9ih1ksFGIjC8-EVbOv8F3ZAwmOBOLIuxrn-mUyNlssnFL5fnKE4g2kkPE0Cw852zcyvPUMX9uHPOhG7clsxoPUFz5QhG-CTT7UBRSbfjIMhg_aKGddjpY5y2u7XDwFkg7UK2cH6_Vy-zdJQ_1iwK6MxvZVqNNP8fGBVpGIjh1OemKgqejcgcE-4TEs6r9pS6fDtZbR3lX2MdFfc1GaTNOFPdaVyt85uFAXYiK9of3paraiQZV-MzP1t1KhvH-9urH-AfbdXkXKW5VHVOlsCXNkovfBg4vWtTZerHbsaii3cpfGgta19YZqo7TAJDPvb9Jk9zYbXTbPLu0zRA67xWm7KPmkYt-jlRI7HbYer5V1gptT9ABS7-0Fl9Klsg5Y7NBYgjiwdE2RDgOBDKGzSAuuITZ_Z63_ZfpCnzpbBHBnDeSA667T-M0B9MMXy-F8kVzMGdr9gSK0j7Hh-qXolZzvZGsjzuLvE_IGa7L3wpLQtkSgRUUSKBC0ZztxYBnWCJYBoUgJSpR_0WqIfcMyJ3PhctWgIM3sNlvJLdBqeHS3HSe0x8NmcuSKaA1frx3U80Qbtws257tkogTfZNw00iiRVc-UNMDKYXs5xCYI0tBMemVxc_fyddo_fW4_1n2UxIxRG4ic_OCoUD9QBoyisqvM3boFPuwTLRUv1bTIOz6Fxi0LuQcL2jwhpj-wtL8PbMCFDbpFJXM-e-1LHkMyVwAs1mgYoUN0XR_Q0n0vJnRr0f5gyYGg6mYpm4IuAFQi_mnDm_Pk1LxJxYvs0_tvzWdBJ0PqVIi_-A0JaTMfJkKg1gtCXyDAxDi6EXp79DLK87_n8-nuRwi5kPgOLjOgIOh_I4GuczjIBQ9octbH001l5plwtdh66_pDKtOE3MhRJQXPiSubZc-hebhMHvM2Pmb8tzW8DV9vGIGL_UAt-UXy8ACGwmT2pscfx1dVDQSNn6UW5CLDw8E2UMekmV3g23Dq1IvWgYoV33hoZTdBFu6-amwy1zcQzNxocfz5gC00DJpeKMbWeGNgCAYyP6YDDxII4ok011hVw_R8GACgjnU2ZlxcbjG3GYo64mYkwD2qUOJj1V-SBfEGixyPf3-JCGxkXYTEiB_jaaLaF7o6vExCcOow53LbrLC0XHzeLYAM4Z80saUXUiFopL_pVi25DejZ2n2C3hrhkYwEbiwhcs5CXb8zdrZZf-MtXpfcJs-5BDoF4BJH0qUCNrY3s8yX6Mi8YwAeLK2llKgZqOMfAxpeKnrYjPt_bkx6cuj5HmSd4_Gv1ufepw6avLApSVDyuOEIXkRRPr_dYrJgl1ELuPcf-9ms7FCiN4k7ZuNonqUeZW1qF5VRpdwhOz03ElZNrYM9-a2acGJsa7_P7W_r1xGAAejoOMkjG0IJWhcF9AF8wwN1CWlLgrywbK0BCdMimhwZTBISmdfsvRC9WCvxPzbh5z-Dsus6eqiN45bN-g_GlF_dZ5QAkAN0pav8sO8xdkMsRMdmpZBRMIDAxXT3Pg-NoscegYvDFnVn6G3qHUV7BF2p5OlSL8qMYIXFiuerHPVynw1mtGIUGeRlBnizpRRXW0N938ifIu_XEDPArmAhxg_kp9A833At0uaAsznya-6toLJ-RskUfO1CZvoBC-WGNe_pnAhrhmfIeeUn6NDDrFZeJ3-lbeRWJVTwmSELd_DO7y-gqod0GScchW8cn93vYp_C40Qw27Vy2i4y4k04Ved4vrD7grk0gAaVtjoP2UrRtQAdKfRqK9GgucIuH8Gs5wBVCNTBkvcaZSZvitBipK6hLxVUihfsxRjiNn0lqrAI3cEw8xaTSRx20gwYE7GwYDboUOlQ9_s8BavcbMCvCAUWpeIYSLmLKsmW8l5G1ySFrB4SIjYhpaw59dj7OI6CmAIYsFceJY3v5J5E6oLa7rhiMFu7nIbIPt6jvwNU3qxmwlsGr2i8zHHwEDhr3WYAzz-FK7sCxfXKjPprOrv5vZl7HPYIlFYUVW7ptIkKNWlZ1pwn6GoJUWWa-JA_HkxCiORBB7Hcogwz5YVFGz1MwIfyE8tC0pRIWhtS3YG29MhAHTM0gcGtiuU0bJdcvNxQERnBbXvz1hQfIdD6F7hjo4RIlJv8An9GBBVF5eB6acdzGBMGrECgeBJCKmvSqfDFnOJA4JNVvGfuDk5jloqGX5KjiB68golqCpFG0dvKm_npQx1-VCz98r1E3uJtHRqc85gd7qIg9UZVQeWqvZ9AWx0RTL3A_g5YzKeSeJCuEJZXDCtoNL9hE6cbfsuKMOaNB9JiSKWd5N-D9Tk1eXmOxd1ulO5zZfE10XRv0lKARkKezX-D8iQMgyMwot23PKhL_cXXTER1s7_-rOuoIzP3opN3OBP5vbhtCVX5LAf7DRiS-1PI945SJspLEcOpNOEkpOg--LadK7vsW2UEW8qrGfjOQkEEVgzzIKspBaI4KA9mjNi4dxAWIzK97PkI910c1GA_gEQadPoD-GKUto0vlBBzyYk_6_MjEsuUqimRhEL5sN7iHqXqY7NhW08iKukYXZSSuXCViatpnYIizoxpTaJ_5StUxpqd8yusdYU8YQOL_pekQv5nMtJtUkze4wOepdsulMuCdefmZ9ZQSJJ8Vj3oUKfMW-ZLB0Pkcsg1e5oK0ijrUn-MqXSiA1EUZjW1hrkPwONV8lBz8F-3ox8dygAwMnS8VW0VhClfXeINivGgvEUjpmhml6CMbSm60Jocwh3l3z78zD4em7rzCCmgNXMihczlPCWUvc1MYp6XXmm18Oc9yK5MmiA1m7oqAx3-LENJQAUUFE4Ks143-Nw3--wCQSQKv0e9qEWXNVw4qrqIGOzq0NUNG2mUgw3nhb7tFQZCLAvWPKfEYwzhDwKz1PMExuSZwbHi0iTj9-2zgBdKrHC-XCHCDZNuxo1ed3vdPLAFQOmp7ubp19JBAmOAGTUEPf8z3Wdfc3ue-dqEd8r6ixRLb93BdLNq18vw6OY55SzQiIQHBJTMr8slE0kMH3paHVJ3ZMrVrgdyYad-rj2fa-qisjK83eryd-cr8BAcS-wQ2kSgORVojRoRFAsV4urFDSfX64ueGqQVnbZVFo6R1lKKgEJtRFNyEwdqkRiDCo6jxzpjKpweJr3IgudNcq1BM4y-X9S6TtBXruKC7ert0UZxnqR88NE6H5vFYpTY_DgqrLPiGgR_OQyE01sS1OWUqlYVNpqgP9ynL51hD7Xpg_7PTi7NUmXX4cStn8JdTq2EYNSuksRff7prLPOnYM9zSMMBwB68bmWEkkh2sophCvRgj-ZVm_fholmu799wdv7ZLLU_AgTwkJD2zIHcuqydjLvnVQxW-K2fIT9wXyHK41AtDCho0pOGLGUR0-g4tGjpT3PlKhSCldU6cw3yLIeLHk5eT0NCKITH0rn5vzY2D36qVXRS5PzJShdvoaHCKUdUihwhFMxZ8zYbDNnBbvnJc5TlMlJ1dLvxGV1ddbIl2wDnS7MrvnIWn5WqVUN5jk1Aca5swP_9E0d4HiZJzjHozDjsiysGI4rPA5IRGDCzZs4Ch211VMRmFLRN4EodLrebTyDFnUEOMK7ypA4oJY0sE2MmYKfr21rko5BFnUzrohtaC4Hczvu1AnGsSEyyCEWGRyYFm5KgFkXEttOQyxu3TNoEN4i3rIgSQu3BeZgvQwnEj8zWJfnvb5pk31od4ayhm60D2by9XAMb9nZsLTPBHkBhfOZO5xGDVuvrby1XQElcySDPfNun3rk0iTbWo7D9RengPAsc1_OKJsFj23HKXHgnP7EjhHs3Hc7zWh2uUy9rg5tUEz0USqsNP2g7EwEoK9nee7VgOsLA0CqXj9AonBRuJIlsiZ3opuLvsO2zxQfZjSkkT7Jx-V3J1R-wBeiv2RjNhEEUUERGOF-H0a4d5Kj_w9SXRtzBkkeMVssxjjj9gQ6Fgc2AjfEudfyCrMjeGei-ExLIz_uzvfvRJk9lHNosTnwYURq0VMaEqJlF9W-kSFcAM9J6e9ii9z7RKU7G6uODeO1E5lO45wKqqErNTEv4BIL75mWAwcfRJ5suD9_F_NA","summary":[{"type":"summary_text","text":"**Clarifying release tag commit**"},{"type":"summary_text","text":"**Analyzing cross-version broker compatibility issues**"},{"type":"summary_text","text":"**Evaluating opener eviction and attach risks**"}]},{"id":"rs_099a7567dadef391016a5974685ab4819b92cc5bec685f0755","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXRzcRw3PEmiEhsTpfCf309jLwy5dq-fFaprzy_xRAf6t_XyWNHuhqCJfdj38VppPXDjuMDgmu9wg4Gsg3ceQJgKXVb6bGQNqkyyHHNUP0fcrlsqxh2rUM07v-OD_WdHlJ-BqvKPDAFfh9YJ8Uz1m0E7p6u9H8mywgp3CHirloQKBbAsMNZqBDVdY3BN4bC7IN1niXTkrSi7bbIu14lWiR1W1YBvgAX557WZpBKRsQjMU-QNpVy-Phw0wkDKsJpPdal-Z6BB0ZWs9DdxkPZEuEIGp7eM4mZ95IkjJHjYX5vBTY8x_l5c2CjQOQ8GxND0MYoQww2OHCNMwRVIMpzxYpgNAXwHWd90l9bA0-FM6UH3qPjrNAM9Ibm1H4aBuhrIU0LjxzkS3e-cuo7RVOMj2H7J1-N59BKuM5fXzNx8xCb_WjHMMzDWvor0yaeGj-12HtgaeZBPSv3Z9gKMMNQI-AEszIuLzogBQPjsZvzlDYkfXX4pZRZ4PSLK4Vvme6vYX-_IFO2uGfW-3k5xBwxZfkKXEgaMRnhSRQnEYno_TnXQWsLqmmalmUoA_47SavHf_2AppqkchHYr3WCDx20nQAc0Da2SZEpCCPDFMfCTgtXjeXNXpN3xClL2X47NLctP5RT1y40eHaAClPU7AUXk1tIUNXUTHy7FIDBz2gscF1EATwBZOVA_D1FZCuj2AHsuUNjOschFTeSX_nDJ0udHBrhqM80zC1Fw3uW3knvJmm3ocFNDWEpbA9whA_dssTgy20dFSSiQL064dzi0UJSMfkapgegpj47XVjJ5Er3pjwZa-gadO5M2Q9Wr1KTUO6KE_UYleyrjE-B22Uq2eJZ82W2a5kzW36ongwtWOp6VSWKRe-AmFtD7qyuJ5-kpsqx-qj0aqlXYHExnjKweArBIjstOTWr8QySeTktkjbhG7MCP1x1N9-BpiCYrrw6-q0eKjvRqUCfC3F-5QwIsi29SSpoMVPpy_ltU2WeV5XBM84swz68Nl8RzmpBaVNe8_I1MNYoQHYt3TkSRPwabuf4KqfQZh2vqTyuafJw-LI4XYg2iXu-Onk10P01E8ylk2ry4STXvRtAyzs51Xzj62islSKtZGrYcJ6mgNP-4yzBaj69LfAK1fl68V_1zsQHwvbtSli0cdLZ3rFI7rHFIZQs-1AWXwEzrBV34XwvBsZY_bljFSk5DinnyJxLYthReSembNNHdmKuxRrFKzct9OXik69rTwHE6h1pW2uczb_tJ1DaMarAtSxKV2VOIfle8AIgrczLzd13s21sPfG7EADGLaX1mCk_fXRK4-idy9X-Qg53Ky5a76uz3BFphZdbEfFNfuufbHqBHaY5uk_PQZE94y2mvSfaUAfnWJih6rX0O0247WRFWId_GYrAPNRBrLTAZoeW3yG1xkRcB0Xmye3fXoLpSXIrS766sZtw4vZcNYgO_VkK0o20gwRfFqw9Ct-VGxgOcrxxsbG6N1pB4-fzjdq9tt2SCZ_jPGsw7BsGNFjL78DoSwTYdqxOguMuxspCz7wRg2UzAxwJk8kbUQ-miwOOSJF_s30xWJYSn8BETisv5uIFg3GrH6k9ppmeWSbCRmsXA_-JYJT8qeZrLnxNnXIPat2phldgip8Dm-n6Gkwsih04Nc96J910FWYp7IifFP2dV7hwmvwHUZCyDQ_XklS71gRRwJ5nlA301Gf0DHzBbYcNNCFv8ZCKuyQ6thue1DdUB0ZID3OMIXkrNKtcft3UKwE4dtg5r1ymPskmWbe3atRbgvqBeap_hXE8bLkTh281aalYlGqh-0GYiPiynjBK7U82OCApiVmS4eYFP10zRR51Kv5b-ucaiJnMqGL0m60X28yb_pgWz1VkWsyJSnjWvvU71nbiHpRBhMNw5J84dRIcALHKOMjUBggyGj-ZLDMJMOmKIFuiPS7JYTQjoH5ZdA5JcMfjo8kyMmyGl7X8ZZFcxcvmr8VPNqBzdiHOGEwM3VgR0qxBTaXv8y4mQ7Q_0GziaQIT2hhCI9mMXpuRdtRiL8fYbekw3D4Pw57HNTGfertZPwGBejcHXQVS2uWwRegQiwNxURJ3FK_62kmD3QehkMmg54gAMuIkYyrdArhK7gNpaSt8r_VWiUoUGtNfy39wR4Ce4WTE-6XUAhsD4XCn2ZsBzCMVCcJXLBHEgTtW5Irp-THH9c4QSOPUlkdoIdf1fWK_r54vPrJhG821cG-N6SiACpLkuiW3a1oofCKNVYydhessohAiaOFkCq-ssD8_6MOH9MX4MnlrcK2p67XHQpznPS0qpv9WRid68KGHLehhagvMjPhDs2eHuMskhkUdBE9HHj6U63iwhEWVpms8=","summary":[{"type":"summary_text","text":"**Analyzing fallback stall causes**"},{"type":"summary_text","text":"**Investigating 20-30 second stall timing**"}]},{"id":"fc_099a7567dadef391016a5974738b94819bb8c12b994a7f6cb8","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading requirement release entries\",\"path\":\"traceable-reqs.toml\",\"selector\":\"2390-2452\"}","call_id":"call_PJ6naFEsReuduwvkWEkeTLuP","name":"read"},{"id":"fc_099a7567dadef391016a5974738ba8819bbd32fc7731d33029","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading lifecycle hazard timing\",\"path\":\"docs/KNOWN-HAZARDS.md\",\"selector\":\"620-662,676-716\"}","call_id":"call_V7CGQmcMZLtwCpvrgrueHwEj","name":"read"},{"id":"fc_099a7567dadef391016a5974738bb4819b8fa8da148580d2ae","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding brain promotion timing\",\"pattern\":\"TRIAL|trial.*Duration|READY|DRAIN|15s|15 s|30s|30 s|old_gen|promotion\",\"path\":\"crates/spt-daemon/src/brainproc.rs; docs; traceable-reqs.toml\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_tFGSIrmOB3RnCSIBPv6ATvsu","name":"grep"},{"id":"fc_099a7567dadef391016a5974738bb8819b9a1abc5ae98b4c47","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading Redispatch test harness setup\",\"path\":\"crates/spt-daemon/tests/redispatch.rs\",\"selector\":\"1-200\"}","call_id":"call_L4n1lExWX59Eih3xxGdfltrP","name":"read"},{"id":"fc_099a7567dadef391016a5974738bc0819b8fb83efc65fb323d","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading opener IPC behavior\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"1438-1505\"}","call_id":"call_UJt6vJp4AJxM3tVfzomqqUh8","name":"read"},{"id":"fc_099a7567dadef391016a5974738bc4819b9699dbe56d0f1c23","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream retirement implementation\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"120-282,535-558,1548-1605\"}","call_id":"call_9OtqiNkClcpbFHPdz1QYk7bI","name":"read"},{"id":"fc_099a7567dadef391016a5974738bcc819ba690ad2840d2fad8","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking mixed-image coverage\",\"pattern\":\"older broker|old broker|N-1|mixed|v0\\\\.33|predates|unknown command|net_stream_opener|net_stream_retire\",\"path\":\"crates/spt-daemon/tests; crates/spt/tests; crates/spt-daemon/src\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_CCrSlQTRMzvqA3pEEy52vDd6","name":"grep"}]},"duration":23372.341800004244,"ttft":1118.6180000007153,"contextSnapshot":{"promptTokens":104804,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_PJ6naFEsReuduwvkWEkeTLuP|fc_099a7567dadef391016a5974738b94819bb8c12b994a7f6cb8","toolName":"read","startedAt":"2026-07-17T00:16:51.601Z","args":{"path":"traceable-reqs.toml"},"intent":"Reading requirement release entries"},"id":"7ced4b3d","parentId":"a16d346a","timestamp":"2026-07-17T00:16:51.601Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_V7CGQmcMZLtwCpvrgrueHwEj|fc_099a7567dadef391016a5974738ba8819bbd32fc7731d33029","toolName":"read","startedAt":"2026-07-17T00:16:51.601Z","args":{"path":"docs/KNOWN-HAZARDS.md"},"intent":"Reading lifecycle hazard timing"},"id":"ad50a763","parentId":"7ced4b3d","timestamp":"2026-07-17T00:16:51.601Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_tFGSIrmOB3RnCSIBPv6ATvsu|fc_099a7567dadef391016a5974738bb4819b8fa8da148580d2ae","toolName":"grep","startedAt":"2026-07-17T00:16:51.601Z","args":{"path":"crates/spt-daemon/src/brainproc.rs; docs; traceable-reqs.toml"},"intent":"Finding brain promotion timing"},"id":"ad83202d","parentId":"ad50a763","timestamp":"2026-07-17T00:16:51.601Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_L4n1lExWX59Eih3xxGdfltrP|fc_099a7567dadef391016a5974738bb8819b9a1abc5ae98b4c47","toolName":"read","startedAt":"2026-07-17T00:16:51.601Z","args":{"path":"crates/spt-daemon/tests/redispatch.rs"},"intent":"Reading Redispatch test harness setup"},"id":"dab58817","parentId":"ad83202d","timestamp":"2026-07-17T00:16:51.601Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_UJt6vJp4AJxM3tVfzomqqUh8|fc_099a7567dadef391016a5974738bc0819b8fb83efc65fb323d","toolName":"read","startedAt":"2026-07-17T00:16:51.601Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Reading opener IPC behavior"},"id":"f45baffa","parentId":"dab58817","timestamp":"2026-07-17T00:16:51.601Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_9OtqiNkClcpbFHPdz1QYk7bI|fc_099a7567dadef391016a5974738bc4819b9699dbe56d0f1c23","toolName":"read","startedAt":"2026-07-17T00:16:51.601Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading stream retirement implementation"},"id":"ab144fe5","parentId":"f45baffa","timestamp":"2026-07-17T00:16:51.601Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_CCrSlQTRMzvqA3pEEy52vDd6|fc_099a7567dadef391016a5974738bcc819ba690ad2840d2fad8","toolName":"grep","startedAt":"2026-07-17T00:16:51.601Z","args":{"path":"crates/spt-daemon/tests; crates/spt/tests; crates/spt-daemon/src"},"intent":"Checking mixed-image coverage"},"id":"c565da87","parentId":"ab144fe5","timestamp":"2026-07-17T00:16:51.601Z"}
{"type":"message","id":"1759c2cf","parentId":"c565da87","timestamp":"2026-07-17T00:16:51.651Z","message":{"role":"toolResult","toolCallId":"call_V7CGQmcMZLtwCpvrgrueHwEj|fc_099a7567dadef391016a5974738ba8819bbd32fc7731d33029","toolName":"read","content":[{"type":"text","text":"## 7. Boundary & delivery integrity (added 2026-05-31 — Stage A red-team)\n…\n### 7.35 The cached ceremony-clock NTP offset must NOT survive an OS clock STEP — an offset measured against the pre-step clock strands every pairing for the TTL  `[REQ-HAZARD-CEREMONY-CLOCK-STEP]`\n- **Failure (paid-for, field evidence — the enlyzeam BIGNET-join RCA 2026-07-06):** enlyzeam's `w32time` was STOPPED, so its system clock drifted +3m30s; the pairing ceremony offset (REQ-PAIR-8, `ntp.rs`) correctly corrected the TOTP clock — until an operator `w32tm /resync` STEPPED the OS clock back −210s under the LIVE daemon. The cached offset had been measured against the OLD (drifted) clock and its snapshot was monotonic-only (`when: Option<Instant>`), so the step was invisible to the cache: for up to the 15-min TTL the ceremony clock stayed ~7 TOTP steps in the past, every `subnet join` returned NO_SEED_HOLDER while fresh-process probes met in <500ms, and only a daemon bounce (which forced a fresh query) healed it. Timeline-proven: refresh cadence 15:0…\n- **Invariant:** the offset cache MUST detect an OS clock step and re-query, never apply a stale offset across it. The snapshot stores an `(Instant, SystemTime)` PAIR; on every read, if the wall clock diverged from the monotonic clock since the snapshot beyond a small tolerance (`|wall_elapsed − mono_elapsed| > ~2s`) the OS clock STEPPED ⇒ force an immediate re-query (offset recomputed against the new clock) regardless of TTL. Belt-and-braces at the join edge: `meet_seed_holder`, on search-deadline exhaustion, forces ONE fresh NTP query and runs one final sweep at the corrected step before the NO_SEED_HOLDER verdict — so a stepped-clock join self-heals WITHOUT a daemon bounce. The step tolerance sits far below one TOTP step (30 s) and well above scheduling j…\n- **spt-core mapping:** `spt_net::net::pairing::ntp` — `CachedOffset{when, wall_at}` pair + `refresh_needed`/`clock_stepped`/`signed_secs` (step-detect) + `force_refresh`/`invalidate` (the meet-exhaustion re-query seam); `spt_daemon::pairhost::meet_seed_holder` via `sweep_then_final_retry` (force-refresh + one final sweep on exhaustion). unit = `clock_step_forces_refresh_via_injected_reads` (injected mono/wall reads: a wall step forward OR back with mono barely moved re-queries; agreeing clocks within TTL do not; TTL-expiry and never-queried also refresh — no 15-min sleep) + `invalidate_clears_the_snapshot` + `meet_retries_once_after_refresh_on_exhaustion`/`meet_final_retry_failing_surrenders_once` (exactly one post-refresh retry, then surrender with the ric…\n- **Source:** JOIN-TRUTH W1/D3 (doyle /diagnose enlyzeam RCA; the offset defeated by a live clock step is the D3 of four defects, D1 multihome + D2 loud-fail + D4 verbose-clock the siblings).\n\n### 7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection — a suspended brain conn must not wedge control  `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]`\n<!-- [doc->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE] -->\n- **Failure (paid-for, rig-CONFIRMED 2026-07-06/07 — `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When that brain is suspended (or black-holed) the write never returns. The output-driven eviction path (`append` → `mark_controller_gone`, bounded by 7.12's `CONTROLLER_WRITE_DEADLINE`) only fires on NEW output, and `reap_dead_controller` (7.29 companion) only caught a writer that had EXITED — so a controller BLOCKED (not exited) on an IDLE session was evicted by NEITHER. Within seconds: attached output froze, `detach` did not release the control stamp (release routed through the wedged conn), reattach was REFUSED (`controlled-by` latched), `rc --take` hung, and `d…\n- **Invariant:** no broker client ever observes a wedge because the BRAIN stalled. (1) The controller's writer publishes its IN-FLIGHT-write window (`write_blocked_since`: `Some(Instant)` set immediately before the blocking `write_frame`, `None` after — the mutex never held ACROSS the write); a reader sees a `Some` older than `BRAIN_WRITE_DEADLINE` (15 s = 3× the 7.12 controller bound, measured full-with-**zero writer progress**, so a slow-but-DRAINING brain never trips it) as a WEDGED writer and stall-evicts it. Eviction is triggered TIME-based, not output-driven: at the take/reattach path (`resolve_subscribe`, so control self-heals against broker truth even on an idle session) AND at the `KIND_SESSIONS` reap. (2) The stamp release is BROKER-side — `clear_c…\n- **spt-core mapping:** `broker.rs` — `ControllerSink::write_blocked_since` + `controller_writer` (marks the in-flight window around both the initial-batch and live-loop writes) + `controller_write_stalled` (pure predicate over injected `now`/`deadline`) + `stall_evict_controller` (Inline stamp at `resolve_subscribe`, Deferred stamp at `reap_dead_controller` so the reap closure does no I/O under the shared sessions lock) + `record_stall_evict`/`stall_evict_stats` (the tally) + `dispatch_stall_evicts` (`KIND_STALL_EVICTS` IPC); `brain.rs` `Brain::stall_evicts`; `cli.rs` `render_stall_evict_line` (daemon-status surface, public wording). unit = `controller_write_stall_predicate_distinguishes_wedged_from_idle` + `stalled_incumbent_is_evicted_on_reattach_and_cont…\n- **Source:** LIFECYCLE-TRUTH W2 (doyle rig `NtSuspendProcess` root + design ruling `docs/W2-DESIGN-RULING.md` @60ec0ed — minimal-plus scope, `BRAIN_WRITE_DEADLINE` 15 s, passive resubscribe + observability rider; todlando build 2026-07-07). The v0.27→v0.28 update-wedge night's flagship defect.\n\n### 7.37 The Layer-1 settle-gate must RE-ARM per delivery on an observable PTY — a mid-session reader reattach (`/clear`) re-creates the head-swallow race  `[REQ-HAZARD-INJECT-SETTLE-REARM]`\n<!-- [doc->REQ-HAZARD-INJECT-SETTLE-REARM] -->\n- **Failure (paid-for, field-CONFIRMED on 0.29.0 — doyle diagnosis + perri screenshot `WindowsTerminal_6iSjya8pMt.png`):** the shipped W5-A settle-gate (7-of-W5, `REQ-INJECT-MULTILINE-INTEGRITY`) gated Layer 1 behind a worker-local ONE-SHOT (`settled_once`) on the premise that the head-swallow race is a STARTUP condition (the input reader not yet attached after spawn). That premise is FALSE: a mid-session `/clear` re-enters the harness's raw-mode input reader, re-attaching it and RE-CREATING the pre-settle window — but the one-shot had already fired at spawn, so `settle_before_inject` was SKIPPED and the head was eaten again. A checkpoint-wake payload injected right after `/clear` arrived head-truncated (mid-path `spt/Cargo.toml)`); echo-verify (Layer 2) is …\n- **Invariant:** the settle-gate re-arms before EVERY delivery on an OBSERVABLE (echoing/interactive) PTY — the bug-prone class, where re-settling is cheap (a tick or two once the reader answers) and necessary (a mid-session reader reattach must be re-confirmed). The steady-state settle is latch-skipped ONLY where the probe is known UNOBSERVABLE on this PTY (a non-echoing ConPTY whose DSR answer never surfaces): there is no reader-reattach race to guard, and each settle would burn the full `INJECT_SETTLE` deadline. The class is discriminated by the settle's OWN return value — `settle_before_inject` returns `true` iff the session ring advanced (probe observed) — latched on the FIRST attempt only; a RE-DRIVE (`attempt > 1`, reached only on a swallowed head) AL…\n- **spt-core mapping:** `broker.rs` `run_inject_worker` — the `settled_once: bool` one-shot is replaced by a `probe_unobservable: bool` latch (init `false` = assume observable → settle first delivery); the gate is the pure `should_settle(attempt, probe_unobservable) = attempt > 1 || !probe_unobservable`, and the first-attempt settle's `observed` return sets `probe_unobservable = !observed`. `settle_before_inject` gains a `bool` return (observed vs timed-out). unit = `should_settle_rearms_on_observable_pty` (observable PTY re-settles each delivery; unobservable-probe PTY skips the steady-state settle; a re-drive settles in both classes — RED-first: restore the one-shot and the observable-PTY assert flips). No int edit: the `inject_control_wedge.rs` mocks (`fi…\n- **Source:** post-0.29.0 field fix (doyle diagnosis + design ruling `BUILD-SPEC-inject-settle-rearm`; todlando build 2026-07-08). An impl bug in the shipped W5-A Layer-1 fix, not a new invariant class — the settle-gate was correct but under-armed.\n\n### 7.38 EVERY write on a physical broker connection is bounded + cancelable + poison-on-failure — no writer holds the send gate across an UNBOUNDED OS write  `[REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]`\n<!-- [doc->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK] -->\n- **Failure (paid-for, field capture + deterministic Windows repro 2026-07-09 — hertz RCA, UPDATE-WEDGE round-4):** `controller_writer` held the `SharedSend = Arc<Mutex<SendHalf>>` guard ACROSS a blocking `write_frame`. On Windows, `interprocess 2.4.2` routes the send to `WriteFileEx` + `SleepEx(INFINITE, alertable)` — this local-socket send path exposes **no supported write timeout** (`set_timeout` → `Unsupported`). An `rc --take` controller consumer that stopped reading blocked the write **indefinitely** (~127.95 s in the capture, released only when a brain restart tore the connections down). The 7.36 logical `stall_evict_controller` removed the controller ROLE but neither canceled the in-flight pipe write, closed the physical connection, nor called `Cance…\n- **Invariant:** every write on a physical broker connection rides ONE broker-owned bounded/cancelable framed-write primitive with an **independent out-of-band aborter**. The `SendHalf` never leaves the conn object (a bypass cannot compile). An ABSOLUTE deadline stamped at write entry covers BOTH the serialized gate-wait AND OS write completion; a per-conn watchdog fires at that deadline and aborts the in-flight op — never relying on the write returning or on opportunistic stall-evict sampling. On deadline / partial write / cancellation / unknown completion: (1) poison the whole physical conn idempotently, (2) abort read+write so `handle_conn` reaches existing EOF cleanup, (3) wait for the canceled op to report completion before releasing its buffer, (4) NEV…\n- **spt-core mapping:** `spt-daemon/src/conn.rs` `BrokerConn` (Condvar-gated serialized write gate + poisoned state + per-in-flight op id + watchdog + `abort_physical()`: cfg(windows) `CancelIoEx` → completion handshake → `DisconnectNamedPipe` raw kernel32 externs; cfg(unix) `UnixStream::shutdown(Both)`); `broker.rs` `SharedSend = Arc<BrokerConn>` + `controller_writer`/`viewer_writer`/`send_frame`/`send_error` routed; `nethost.rs` stream-log/presence sends routed. int = `brain_decouple.rs` `non_draining_controller_stall_evict_releases_writer_and_connection` (`#[cfg(windows)]`, the hertz 9-step: real PTY burst, black-holed remote controller, exactly one logical stall-evict via an independent draining conn, the black-holed conn's `handle_conn` finishes ≤2 s of…\n- **Source:** UPDATE-WEDGE round-4 (hertz root doc `docs/UPDATE-WEDGE-ROOT-CAUSE.md` @f8596ca; doyle gate ruling `docs/UPDATE-WEDGE-RCA-DISPATCH-todlando.md`; todlando build 2026-07-09). The residual 7.36 left open: logical eviction without physical retirement.\n\n---\n\n### 7.39 Per-session identity env (`SPT_ENDPOINT_ID`/`OWL_SESSION_ID`/`SPT_AGENT_ID`) is NEVER inherited — the daemon scrubs it at startup AND on every role spawn, regardless of any role's declared `env_remove`  `[REQ-HAZARD-DAEMON-IDENTITY-ENV-SANITIZE]`\n<!-- [doc->REQ-HAZARD-DAEMON-IDENTITY-ENV-SANITIZE] -->\n- **Failure (paid-for, perri field RCA 2026-07-09/10 — F-036 psyche seat-theft):** a daemon restarted from inside an agent session (routine during core dev / `spt update apply`) carried the session's `SPT_ENDPOINT_ID=doyle` and passed it verbatim into every `[session.psyche_resume]` spawn — core stripped only each role's DECLARED `env_remove` list (runtime.rs `command_for`), so ONE adapter `env_remove` miss infected the whole node. Every psyche claude turn fired SessionStart, the adapter hook saw the leaked endpoint id, took the bind path, and ROTATED the victim's perch onto the psyche's own custody sid with a valid prior-sid proof — every pulse. Field blast radius: lia/deployah/doyle psyches ALL briefed as `<sptc-active-perch id=doyle>`; 37 peer msgs draine…\n- **Invariant:** the identity set (`spt_runtime::IDENTITY_ENV_VARS`) is per-session state and NEVER correct inherited state for a daemon or any child it spawns. The daemon (broker AND brain entries) scrubs its own process env first thing (`scrub_identity_env`); the shared role-spawn command builder scrubs the set unconditionally and LAST (after `env_remove`, read-env stamps, and the recursion guard), so no adapter declaration — missing or pathological — can leak identity into a role child. A spawn that NEEDS an identity var (a hosted harness) receives it by explicit per-spawn injection (manifest `[env] direction = \"inject\"`), never by inheritance. The same startup scrub also clears the inject-echo knobs (`spt_runtime::INJECT_ECHO_ENV_VARS`: `SPT_INJECT_VERIF…\n- **spt-core mapping:** `spt-runtime/src/runtime.rs` `IDENTITY_ENV_VARS` + `scrub_identity_env` + the `command_for` unconditional scrub; `spt-daemon/src/daemon.rs` `Daemon::run` / `Daemon::run_brain` startup scrub; `spt/src/api/startup.rs` `establish_perch` custody-squat refusal over `spt-store/src/psyche_custody.rs` `custody_squatter`.\n- **Source:** MSG-IDENTITY W1 (perri F-036 RCA; doyle dispatch `docs/MSG-IDENTITY-DISPATCH.md`; adapter half shipped claude-spt v0.18.8 — this is the core-layer defense so no adapter miss can ever leak identity again).\n\n---\n\n### 7.40 A commune/signoff drop is deleted ONLY after every applicable tier is durably committed — an un-committable slice preserves the drop, never delete-then-lose  `[REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE]`\n<!-- [doc->REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE] -->\n- **Failure (paid-for, perri field finding 2026-07-08 — F-032, data-loss):** `ingest_drops` unconditionally deleted the drop after `route_slices`, but the project tier is GATED on a non-empty `project_id` — when the endpoint's anchor cwd was unresolved/owlery-internal at ingest time, the `<project-context>` slice was parsed but never committed, yet the source drop was still deleted → the project-context content permanently lost. perri's two-sliced echo-commune INGESTED (file deleted) yet never surfaced at her next SessionStart resume-pull; adapter exonerated. Legacy spt held commit-first-then-delete parity; the modern two-slice ingest broke it.\n- **Invariant:** delete-after-commit, per slice. Distinguish a write **SUPPRESSED by precedence** (incoming older than durable → already superseded → safe to delete) from a slice **un-committable now** (empty `project_id`, or any write error → the `?` retry path). An un-committable non-empty project slice is preserved as a **project-only pending slice under the COMMUNE suffix, whatever the source drop's kind** — a signoff-suffix pending would be reaped by `sweep_stale_signoff` on the next listener start (the signoff sentinel is write-once-per-generation), recreating the black-hole on the signoff path; the commune-named pending is a pure project-slice carrier both suffixes ingest identically and the sweep never sees. The already-committed live slice is droppe…\n- **spt-core mapping:** `spt-live/src/ingest.rs` `ingest_drops` (deferred-project preserve + `Ingested.preserved`); `route_slices` project gate unchanged (`REQ-STORE-CONTEXT-BRANCH-FILL`); kin `REQ-HAZARD-DROP-FILE-SINGLE-WRITER` (core stays the single writer — the preserve rewrite is core's own write).\n- **Source:** MSG-IDENTITY W3 (doyle triage, code-grounded ingest.rs:156 gate vs :200 delete; repro fixture F-032-commune-2026-07-08T222721Z.md).\n\n…\n\n…\n### 7.42 A node holding a valid roster address for a peer is NEVER route-less — a failed dial must not delete the only bootstrap route  `[REQ-HAZARD-MESH-BOOTSTRAP-TRAP]`\n…\n- **Failure (paid-for, hertz field RCA 2026-07-10 — HFENDULEAM + ENLYZEAM fully sequestered from every subnet member, symmetric, green-status):** the pump resolved dial addresses from the exact `peer-addrs.json` entry else id-only discovery — never the valid `RosterEntry.address` — and every `PRESENCE_DIAL_FAILED` unconditionally `drop_seed`'d the cached entry, while the cache refilled only after a future successful seed-proof connection. One transient failure converted a warm route into id-only-or-nothing; with id-only discovery stalled, isolation prevented its own repair. Both rosters held correct direct addresses (Tailscale ping ~12ms) the entire time; `net_up: true`, fresh heartbeat, normal durable counts — every status surface answered a different quest…\n- **Invariant:** dial-address resolution is the ROUTE CHAIN — exact cache → validated `RosterEntry.address` (id-match required) → id-only discovery — always fully consulted in order. A failed dial demotes a cached route (suspect), never deletes a sole route; removal happens only via validated-fresher replacement or roster tombstone. Validated roster addresses reconcile into the cache at startup and on roster merge — recovery never requires an already-successful connection or operator state surgery. `outer key == address.id` is enforced on load and write (repair-from-roster or reject, loudly; migration never bare-deletes the file). Peer-failure telemetry is stage-split and stamped; health reports live peer count + last real progress, and the incident's exact …\n- **spt-core mapping:** `spt-daemon/src/pump/mod.rs` `resolve_submit_addr` + `presence_state_effect` (`PRESENCE_DIAL_FAILED` arm); `spt-store/src/peeraddrs.rs` `PeerAddrStore::put`/`drop_seed`; `spt-daemon/src/seedproofx.rs` `gapfill_peeraddrs`; status renders (`daemon status` / `subnet status`).\n- **Source:** MESH-RECOVERY W1 (hertz RCA `.claude/reports/2026-07-10-hertz-session/02-mesh-isolation-rca.md`, doyle seam-verified 2026-07-16; ADR-0039 — amends REQ-CONV-1's falsified \"a stale addr never strands a peer\" drop-on-fail mechanism).\n\n---\n\n## Conformance checklist (condensed)\n\n| # | Invariant | spt-core surface |\n|---|---|---|\n| 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown |\n| 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain |\n| 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection |\n| 2.3 | Handoff argv/IPC version-tolerant (newer brain ↔ older broker) | broker↔brain IPC, self-update |\n| 2.4 | gen_start = now() on cold-start + handoff | per-instance generation |\n| 3.1 | Ephemeral perch cleanup on all exit paths | `ring` (RAII guard) |\n| 3.3 | Echo-commune before INIT_SIGNOFF | daemon psyche loop |\n| 4.1 | Envelope decode order, `&amp;` last | spt-proto (public, wire-versioned) |\n| 4.2 | Parser panic-free + tolerant | spt-proto |\n| 4.3 | Stale registry entries → fallback, never hard-fail | subnet registry resolution |\n| 4.10 | Silent-node registry rows evicted (heard-map TTL); own rows never decay | registry pump eviction |\n| 4.6 | Addressable-id charset reserves `:`/`@` delimiters | `spt_proto::id` at creation seams |\n| 5.2 | tmp-write + atomic-rename + retry (EBUSY) | all state writes, binary swap |\n| 5.3 | Timeout every harness subprocess | manifest invocations |\n| 5.4 | Strip UNC prefix on serialized paths | spt-proto path normalization |\n| 5.5 | ConPTY reader answers DSR (`ESC[6n`) | spt-term broker PTY reader |\n| 7.7 | A wedged viewer is evicted, never stalls the controller/child/drain | broker `OutputLog` fan-out (controller/viewer) |\n| 5.6 | Detached long-lived children spawn `bInheritHandles=FALSE` | daemon + shell spawn |\n| 5.7 | Daemon always unelevated in the invoker's universe (de-elevated spawn + entry guard) | `deelevate` seam, `spawn_detached`, `Daemon::run` |\n| 5.8 | `CREATE_NO_WINDOW` on every console child of the console-less daemon | `gitrun::run_git`, `kill_shell_pid`, `run_bounded_command`, `shellwake` |\n| 5.12 | Native-PTY spawn PATHEXT-resolves a bare program + wraps non-PE targets (.cmd/.bat→`cmd /c`, .ps1→powershell), bypassing portable-pty's shim-first `which` (CreateProcessW os error 193) | `spt_term::winprog` → `PtySession::spawn_program_in` |\n| 6.1 | Single path/registry source of truth | storage layout |\n| 6.4 | Drop files supervisor-owned single-writer | runtime contract |\n| 6.5 | Direct-write precedence marker (+ node id) | cross-node Psyche sync |\n| 6.6 | Surfaced conflicts preserve both versions until dominated | context conflict artifacts (ADR-0013) |\n| 6.7 | Broker + brain are separate processes (brain restart never drops a hosted endpoint at the process level) | daemon process topology, self-update (ADR-0018) |\n| 6.8 | No irreversible durable-state migration before update ready-promotion (pre-ready writes stay N-1-readable) | auto-rollback / durable-state schema (ADR-0018) |\n| 6.10 | Phase-significant loop timing is a durable absolute-deadline grid (no per-fire write; update preserves phase, crash resets, one-shot never resets) | durable loop timing / self-update (ADR-0018 Q4) |\n| 6.11 | Brain respawn execs the applied bytes (canonical exe captured at broker start, not per-spawn current_exe) + promotion bytes-gate (exe_hash == artifact, else rollback) | daemon respawn path / self-update (ADR-0018 Q3) |\n| 7.1 | Local `api` mutation authenticated to endpoint | api surface / broker IPC |\n…\n| 7.22 | Endpoint-stop / brain-death reaps a brain-LESS perch's orphan detached Psyche via the cmdline-scoped guard — the handle-reap (`stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT (the owning brain is dead, so its `psyche_child` handle died with it) and the brain-START sweep (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host runs the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Fail-safe-decline (pid-alive AND basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES — a missed dup is bounded, a wrong-kill catastrophic). The orphan-leak half of perri F-010xF-015 (the psyche own-copy is the other half, ADR-…\n…\n<!-- [doc->REQ-HAZARD-STORE-INIT-RACE] -->\n\n[Some lines truncated to 768 chars]"}],"details":{"displayContent":{"text":"## 7. Boundary & delivery integrity (added 2026-05-31 — Stage A red-team)\n…\n### 7.35 The cached ceremony-clock NTP offset must NOT survive an OS clock STEP — an offset measured against the pre-step clock strands every pairing for the TTL  `[REQ-HAZARD-CEREMONY-CLOCK-STEP]`\n- **Failure (paid-for, field evidence — the enlyzeam BIGNET-join RCA 2026-07-06):** enlyzeam's `w32time` was STOPPED, so its system clock drifted +3m30s; the pairing ceremony offset (REQ-PAIR-8, `ntp.rs`) correctly corrected the TOTP clock — until an operator `w32tm /resync` STEPPED the OS clock back −210s under the LIVE daemon. The cached offset had been measured against the OLD (drifted) clock and its snapshot was monotonic-only (`when: Option<Instant>`), so the step was invisible to the cache: for up to the 15-min TTL the ceremony clock stayed ~7 TOTP steps in the past, every `subnet join` returned NO_SEED_HOLDER while fresh-process probes met in <500ms, and only a daemon bounce (which forced a fresh query) healed it. Timeline-proven: refresh cadence 15:0…\n- **Invariant:** the offset cache MUST detect an OS clock step and re-query, never apply a stale offset across it. The snapshot stores an `(Instant, SystemTime)` PAIR; on every read, if the wall clock diverged from the monotonic clock since the snapshot beyond a small tolerance (`|wall_elapsed − mono_elapsed| > ~2s`) the OS clock STEPPED ⇒ force an immediate re-query (offset recomputed against the new clock) regardless of TTL. Belt-and-braces at the join edge: `meet_seed_holder`, on search-deadline exhaustion, forces ONE fresh NTP query and runs one final sweep at the corrected step before the NO_SEED_HOLDER verdict — so a stepped-clock join self-heals WITHOUT a daemon bounce. The step tolerance sits far below one TOTP step (30 s) and well above scheduling j…\n- **spt-core mapping:** `spt_net::net::pairing::ntp` — `CachedOffset{when, wall_at}` pair + `refresh_needed`/`clock_stepped`/`signed_secs` (step-detect) + `force_refresh`/`invalidate` (the meet-exhaustion re-query seam); `spt_daemon::pairhost::meet_seed_holder` via `sweep_then_final_retry` (force-refresh + one final sweep on exhaustion). unit = `clock_step_forces_refresh_via_injected_reads` (injected mono/wall reads: a wall step forward OR back with mono barely moved re-queries; agreeing clocks within TTL do not; TTL-expiry and never-queried also refresh — no 15-min sleep) + `invalidate_clears_the_snapshot` + `meet_retries_once_after_refresh_on_exhaustion`/`meet_final_retry_failing_surrenders_once` (exactly one post-refresh retry, then surrender with the ric…\n- **Source:** JOIN-TRUTH W1/D3 (doyle /diagnose enlyzeam RCA; the offset defeated by a live clock step is the D3 of four defects, D1 multihome + D2 loud-fail + D4 verbose-clock the siblings).\n\n### 7.36 The broker control plane and PTY fan-out must NEVER block on a single subscriber connection — a suspended brain conn must not wedge control  `[REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]`\n<!-- [doc->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE] -->\n- **Failure (paid-for, rig-CONFIRMED 2026-07-06/07 — `NtSuspendProcess` on the brain, no update involved):** a controller's writer thread does a BLOCKING socket write to its brain subscriber conn. When that brain is suspended (or black-holed) the write never returns. The output-driven eviction path (`append` → `mark_controller_gone`, bounded by 7.12's `CONTROLLER_WRITE_DEADLINE`) only fires on NEW output, and `reap_dead_controller` (7.29 companion) only caught a writer that had EXITED — so a controller BLOCKED (not exited) on an IDLE session was evicted by NEITHER. Within seconds: attached output froze, `detach` did not release the control stamp (release routed through the wedged conn), reattach was REFUSED (`controlled-by` latched), `rc --take` hung, and `d…\n- **Invariant:** no broker client ever observes a wedge because the BRAIN stalled. (1) The controller's writer publishes its IN-FLIGHT-write window (`write_blocked_since`: `Some(Instant)` set immediately before the blocking `write_frame`, `None` after — the mutex never held ACROSS the write); a reader sees a `Some` older than `BRAIN_WRITE_DEADLINE` (15 s = 3× the 7.12 controller bound, measured full-with-**zero writer progress**, so a slow-but-DRAINING brain never trips it) as a WEDGED writer and stall-evicts it. Eviction is triggered TIME-based, not output-driven: at the take/reattach path (`resolve_subscribe`, so control self-heals against broker truth even on an idle session) AND at the `KIND_SESSIONS` reap. (2) The stamp release is BROKER-side — `clear_c…\n- **spt-core mapping:** `broker.rs` — `ControllerSink::write_blocked_since` + `controller_writer` (marks the in-flight window around both the initial-batch and live-loop writes) + `controller_write_stalled` (pure predicate over injected `now`/`deadline`) + `stall_evict_controller` (Inline stamp at `resolve_subscribe`, Deferred stamp at `reap_dead_controller` so the reap closure does no I/O under the shared sessions lock) + `record_stall_evict`/`stall_evict_stats` (the tally) + `dispatch_stall_evicts` (`KIND_STALL_EVICTS` IPC); `brain.rs` `Brain::stall_evicts`; `cli.rs` `render_stall_evict_line` (daemon-status surface, public wording). unit = `controller_write_stall_predicate_distinguishes_wedged_from_idle` + `stalled_incumbent_is_evicted_on_reattach_and_cont…\n- **Source:** LIFECYCLE-TRUTH W2 (doyle rig `NtSuspendProcess` root + design ruling `docs/W2-DESIGN-RULING.md` @60ec0ed — minimal-plus scope, `BRAIN_WRITE_DEADLINE` 15 s, passive resubscribe + observability rider; todlando build 2026-07-07). The v0.27→v0.28 update-wedge night's flagship defect.\n\n### 7.37 The Layer-1 settle-gate must RE-ARM per delivery on an observable PTY — a mid-session reader reattach (`/clear`) re-creates the head-swallow race  `[REQ-HAZARD-INJECT-SETTLE-REARM]`\n<!-- [doc->REQ-HAZARD-INJECT-SETTLE-REARM] -->\n- **Failure (paid-for, field-CONFIRMED on 0.29.0 — doyle diagnosis + perri screenshot `WindowsTerminal_6iSjya8pMt.png`):** the shipped W5-A settle-gate (7-of-W5, `REQ-INJECT-MULTILINE-INTEGRITY`) gated Layer 1 behind a worker-local ONE-SHOT (`settled_once`) on the premise that the head-swallow race is a STARTUP condition (the input reader not yet attached after spawn). That premise is FALSE: a mid-session `/clear` re-enters the harness's raw-mode input reader, re-attaching it and RE-CREATING the pre-settle window — but the one-shot had already fired at spawn, so `settle_before_inject` was SKIPPED and the head was eaten again. A checkpoint-wake payload injected right after `/clear` arrived head-truncated (mid-path `spt/Cargo.toml)`); echo-verify (Layer 2) is …\n- **Invariant:** the settle-gate re-arms before EVERY delivery on an OBSERVABLE (echoing/interactive) PTY — the bug-prone class, where re-settling is cheap (a tick or two once the reader answers) and necessary (a mid-session reader reattach must be re-confirmed). The steady-state settle is latch-skipped ONLY where the probe is known UNOBSERVABLE on this PTY (a non-echoing ConPTY whose DSR answer never surfaces): there is no reader-reattach race to guard, and each settle would burn the full `INJECT_SETTLE` deadline. The class is discriminated by the settle's OWN return value — `settle_before_inject` returns `true` iff the session ring advanced (probe observed) — latched on the FIRST attempt only; a RE-DRIVE (`attempt > 1`, reached only on a swallowed head) AL…\n- **spt-core mapping:** `broker.rs` `run_inject_worker` — the `settled_once: bool` one-shot is replaced by a `probe_unobservable: bool` latch (init `false` = assume observable → settle first delivery); the gate is the pure `should_settle(attempt, probe_unobservable) = attempt > 1 || !probe_unobservable`, and the first-attempt settle's `observed` return sets `probe_unobservable = !observed`. `settle_before_inject` gains a `bool` return (observed vs timed-out). unit = `should_settle_rearms_on_observable_pty` (observable PTY re-settles each delivery; unobservable-probe PTY skips the steady-state settle; a re-drive settles in both classes — RED-first: restore the one-shot and the observable-PTY assert flips). No int edit: the `inject_control_wedge.rs` mocks (`fi…\n- **Source:** post-0.29.0 field fix (doyle diagnosis + design ruling `BUILD-SPEC-inject-settle-rearm`; todlando build 2026-07-08). An impl bug in the shipped W5-A Layer-1 fix, not a new invariant class — the settle-gate was correct but under-armed.\n\n### 7.38 EVERY write on a physical broker connection is bounded + cancelable + poison-on-failure — no writer holds the send gate across an UNBOUNDED OS write  `[REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]`\n<!-- [doc->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK] -->\n- **Failure (paid-for, field capture + deterministic Windows repro 2026-07-09 — hertz RCA, UPDATE-WEDGE round-4):** `controller_writer` held the `SharedSend = Arc<Mutex<SendHalf>>` guard ACROSS a blocking `write_frame`. On Windows, `interprocess 2.4.2` routes the send to `WriteFileEx` + `SleepEx(INFINITE, alertable)` — this local-socket send path exposes **no supported write timeout** (`set_timeout` → `Unsupported`). An `rc --take` controller consumer that stopped reading blocked the write **indefinitely** (~127.95 s in the capture, released only when a brain restart tore the connections down). The 7.36 logical `stall_evict_controller` removed the controller ROLE but neither canceled the in-flight pipe write, closed the physical connection, nor called `Cance…\n- **Invariant:** every write on a physical broker connection rides ONE broker-owned bounded/cancelable framed-write primitive with an **independent out-of-band aborter**. The `SendHalf` never leaves the conn object (a bypass cannot compile). An ABSOLUTE deadline stamped at write entry covers BOTH the serialized gate-wait AND OS write completion; a per-conn watchdog fires at that deadline and aborts the in-flight op — never relying on the write returning or on opportunistic stall-evict sampling. On deadline / partial write / cancellation / unknown completion: (1) poison the whole physical conn idempotently, (2) abort read+write so `handle_conn` reaches existing EOF cleanup, (3) wait for the canceled op to report completion before releasing its buffer, (4) NEV…\n- **spt-core mapping:** `spt-daemon/src/conn.rs` `BrokerConn` (Condvar-gated serialized write gate + poisoned state + per-in-flight op id + watchdog + `abort_physical()`: cfg(windows) `CancelIoEx` → completion handshake → `DisconnectNamedPipe` raw kernel32 externs; cfg(unix) `UnixStream::shutdown(Both)`); `broker.rs` `SharedSend = Arc<BrokerConn>` + `controller_writer`/`viewer_writer`/`send_frame`/`send_error` routed; `nethost.rs` stream-log/presence sends routed. int = `brain_decouple.rs` `non_draining_controller_stall_evict_releases_writer_and_connection` (`#[cfg(windows)]`, the hertz 9-step: real PTY burst, black-holed remote controller, exactly one logical stall-evict via an independent draining conn, the black-holed conn's `handle_conn` finishes ≤2 s of…\n- **Source:** UPDATE-WEDGE round-4 (hertz root doc `docs/UPDATE-WEDGE-ROOT-CAUSE.md` @f8596ca; doyle gate ruling `docs/UPDATE-WEDGE-RCA-DISPATCH-todlando.md`; todlando build 2026-07-09). The residual 7.36 left open: logical eviction without physical retirement.\n\n---\n\n### 7.39 Per-session identity env (`SPT_ENDPOINT_ID`/`OWL_SESSION_ID`/`SPT_AGENT_ID`) is NEVER inherited — the daemon scrubs it at startup AND on every role spawn, regardless of any role's declared `env_remove`  `[REQ-HAZARD-DAEMON-IDENTITY-ENV-SANITIZE]`\n<!-- [doc->REQ-HAZARD-DAEMON-IDENTITY-ENV-SANITIZE] -->\n- **Failure (paid-for, perri field RCA 2026-07-09/10 — F-036 psyche seat-theft):** a daemon restarted from inside an agent session (routine during core dev / `spt update apply`) carried the session's `SPT_ENDPOINT_ID=doyle` and passed it verbatim into every `[session.psyche_resume]` spawn — core stripped only each role's DECLARED `env_remove` list (runtime.rs `command_for`), so ONE adapter `env_remove` miss infected the whole node. Every psyche claude turn fired SessionStart, the adapter hook saw the leaked endpoint id, took the bind path, and ROTATED the victim's perch onto the psyche's own custody sid with a valid prior-sid proof — every pulse. Field blast radius: lia/deployah/doyle psyches ALL briefed as `<sptc-active-perch id=doyle>`; 37 peer msgs draine…\n- **Invariant:** the identity set (`spt_runtime::IDENTITY_ENV_VARS`) is per-session state and NEVER correct inherited state for a daemon or any child it spawns. The daemon (broker AND brain entries) scrubs its own process env first thing (`scrub_identity_env`); the shared role-spawn command builder scrubs the set unconditionally and LAST (after `env_remove`, read-env stamps, and the recursion guard), so no adapter declaration — missing or pathological — can leak identity into a role child. A spawn that NEEDS an identity var (a hosted harness) receives it by explicit per-spawn injection (manifest `[env] direction = \"inject\"`), never by inheritance. The same startup scrub also clears the inject-echo knobs (`spt_runtime::INJECT_ECHO_ENV_VARS`: `SPT_INJECT_VERIF…\n- **spt-core mapping:** `spt-runtime/src/runtime.rs` `IDENTITY_ENV_VARS` + `scrub_identity_env` + the `command_for` unconditional scrub; `spt-daemon/src/daemon.rs` `Daemon::run` / `Daemon::run_brain` startup scrub; `spt/src/api/startup.rs` `establish_perch` custody-squat refusal over `spt-store/src/psyche_custody.rs` `custody_squatter`.\n- **Source:** MSG-IDENTITY W1 (perri F-036 RCA; doyle dispatch `docs/MSG-IDENTITY-DISPATCH.md`; adapter half shipped claude-spt v0.18.8 — this is the core-layer defense so no adapter miss can ever leak identity again).\n\n---\n\n### 7.40 A commune/signoff drop is deleted ONLY after every applicable tier is durably committed — an un-committable slice preserves the drop, never delete-then-lose  `[REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE]`\n<!-- [doc->REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE] -->\n- **Failure (paid-for, perri field finding 2026-07-08 — F-032, data-loss):** `ingest_drops` unconditionally deleted the drop after `route_slices`, but the project tier is GATED on a non-empty `project_id` — when the endpoint's anchor cwd was unresolved/owlery-internal at ingest time, the `<project-context>` slice was parsed but never committed, yet the source drop was still deleted → the project-context content permanently lost. perri's two-sliced echo-commune INGESTED (file deleted) yet never surfaced at her next SessionStart resume-pull; adapter exonerated. Legacy spt held commit-first-then-delete parity; the modern two-slice ingest broke it.\n- **Invariant:** delete-after-commit, per slice. Distinguish a write **SUPPRESSED by precedence** (incoming older than durable → already superseded → safe to delete) from a slice **un-committable now** (empty `project_id`, or any write error → the `?` retry path). An un-committable non-empty project slice is preserved as a **project-only pending slice under the COMMUNE suffix, whatever the source drop's kind** — a signoff-suffix pending would be reaped by `sweep_stale_signoff` on the next listener start (the signoff sentinel is write-once-per-generation), recreating the black-hole on the signoff path; the commune-named pending is a pure project-slice carrier both suffixes ingest identically and the sweep never sees. The already-committed live slice is droppe…\n- **spt-core mapping:** `spt-live/src/ingest.rs` `ingest_drops` (deferred-project preserve + `Ingested.preserved`); `route_slices` project gate unchanged (`REQ-STORE-CONTEXT-BRANCH-FILL`); kin `REQ-HAZARD-DROP-FILE-SINGLE-WRITER` (core stays the single writer — the preserve rewrite is core's own write).\n- **Source:** MSG-IDENTITY W3 (doyle triage, code-grounded ingest.rs:156 gate vs :200 delete; repro fixture F-032-commune-2026-07-08T222721Z.md).\n\n…\n\n…\n### 7.42 A node holding a valid roster address for a peer is NEVER route-less — a failed dial must not delete the only bootstrap route  `[REQ-HAZARD-MESH-BOOTSTRAP-TRAP]`\n…\n- **Failure (paid-for, hertz field RCA 2026-07-10 — HFENDULEAM + ENLYZEAM fully sequestered from every subnet member, symmetric, green-status):** the pump resolved dial addresses from the exact `peer-addrs.json` entry else id-only discovery — never the valid `RosterEntry.address` — and every `PRESENCE_DIAL_FAILED` unconditionally `drop_seed`'d the cached entry, while the cache refilled only after a future successful seed-proof connection. One transient failure converted a warm route into id-only-or-nothing; with id-only discovery stalled, isolation prevented its own repair. Both rosters held correct direct addresses (Tailscale ping ~12ms) the entire time; `net_up: true`, fresh heartbeat, normal durable counts — every status surface answered a different quest…\n- **Invariant:** dial-address resolution is the ROUTE CHAIN — exact cache → validated `RosterEntry.address` (id-match required) → id-only discovery — always fully consulted in order. A failed dial demotes a cached route (suspect), never deletes a sole route; removal happens only via validated-fresher replacement or roster tombstone. Validated roster addresses reconcile into the cache at startup and on roster merge — recovery never requires an already-successful connection or operator state surgery. `outer key == address.id` is enforced on load and write (repair-from-roster or reject, loudly; migration never bare-deletes the file). Peer-failure telemetry is stage-split and stamped; health reports live peer count + last real progress, and the incident's exact …\n- **spt-core mapping:** `spt-daemon/src/pump/mod.rs` `resolve_submit_addr` + `presence_state_effect` (`PRESENCE_DIAL_FAILED` arm); `spt-store/src/peeraddrs.rs` `PeerAddrStore::put`/`drop_seed`; `spt-daemon/src/seedproofx.rs` `gapfill_peeraddrs`; status renders (`daemon status` / `subnet status`).\n- **Source:** MESH-RECOVERY W1 (hertz RCA `.claude/reports/2026-07-10-hertz-session/02-mesh-isolation-rca.md`, doyle seam-verified 2026-07-16; ADR-0039 — amends REQ-CONV-1's falsified \"a stale addr never strands a peer\" drop-on-fail mechanism).\n\n---\n\n## Conformance checklist (condensed)\n\n| # | Invariant | spt-core surface |\n|---|---|---|\n| 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown |\n| 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain |\n| 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection |\n| 2.3 | Handoff argv/IPC version-tolerant (newer brain ↔ older broker) | broker↔brain IPC, self-update |\n| 2.4 | gen_start = now() on cold-start + handoff | per-instance generation |\n| 3.1 | Ephemeral perch cleanup on all exit paths | `ring` (RAII guard) |\n| 3.3 | Echo-commune before INIT_SIGNOFF | daemon psyche loop |\n| 4.1 | Envelope decode order, `&amp;` last | spt-proto (public, wire-versioned) |\n| 4.2 | Parser panic-free + tolerant | spt-proto |\n| 4.3 | Stale registry entries → fallback, never hard-fail | subnet registry resolution |\n| 4.10 | Silent-node registry rows evicted (heard-map TTL); own rows never decay | registry pump eviction |\n| 4.6 | Addressable-id charset reserves `:`/`@` delimiters | `spt_proto::id` at creation seams |\n| 5.2 | tmp-write + atomic-rename + retry (EBUSY) | all state writes, binary swap |\n| 5.3 | Timeout every harness subprocess | manifest invocations |\n| 5.4 | Strip UNC prefix on serialized paths | spt-proto path normalization |\n| 5.5 | ConPTY reader answers DSR (`ESC[6n`) | spt-term broker PTY reader |\n| 7.7 | A wedged viewer is evicted, never stalls the controller/child/drain | broker `OutputLog` fan-out (controller/viewer) |\n| 5.6 | Detached long-lived children spawn `bInheritHandles=FALSE` | daemon + shell spawn |\n| 5.7 | Daemon always unelevated in the invoker's universe (de-elevated spawn + entry guard) | `deelevate` seam, `spawn_detached`, `Daemon::run` |\n| 5.8 | `CREATE_NO_WINDOW` on every console child of the console-less daemon | `gitrun::run_git`, `kill_shell_pid`, `run_bounded_command`, `shellwake` |\n| 5.12 | Native-PTY spawn PATHEXT-resolves a bare program + wraps non-PE targets (.cmd/.bat→`cmd /c`, .ps1→powershell), bypassing portable-pty's shim-first `which` (CreateProcessW os error 193) | `spt_term::winprog` → `PtySession::spawn_program_in` |\n| 6.1 | Single path/registry source of truth | storage layout |\n| 6.4 | Drop files supervisor-owned single-writer | runtime contract |\n| 6.5 | Direct-write precedence marker (+ node id) | cross-node Psyche sync |\n| 6.6 | Surfaced conflicts preserve both versions until dominated | context conflict artifacts (ADR-0013) |\n| 6.7 | Broker + brain are separate processes (brain restart never drops a hosted endpoint at the process level) | daemon process topology, self-update (ADR-0018) |\n| 6.8 | No irreversible durable-state migration before update ready-promotion (pre-ready writes stay N-1-readable) | auto-rollback / durable-state schema (ADR-0018) |\n| 6.10 | Phase-significant loop timing is a durable absolute-deadline grid (no per-fire write; update preserves phase, crash resets, one-shot never resets) | durable loop timing / self-update (ADR-0018 Q4) |\n| 6.11 | Brain respawn execs the applied bytes (canonical exe captured at broker start, not per-spawn current_exe) + promotion bytes-gate (exe_hash == artifact, else rollback) | daemon respawn path / self-update (ADR-0018 Q3) |\n| 7.1 | Local `api` mutation authenticated to endpoint | api surface / broker IPC |\n…\n| 7.22 | Endpoint-stop / brain-death reaps a brain-LESS perch's orphan detached Psyche via the cmdline-scoped guard — the handle-reap (`stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT (the owning brain is dead, so its `psyche_child` handle died with it) and the brain-START sweep (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host runs the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Fail-safe-decline (pid-alive AND basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES — a missed dup is bounded, a wrong-kill catastrophic). The orphan-leak half of perri F-010xF-015 (the psyche own-copy is the other half, ADR-…\n…\n<!-- [doc->REQ-HAZARD-STORE-INIT-RACE] -->","startLine":365,"lineNumbers":[365,null,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,644,645,646,647,648,649,650,651,652,653,654,655,656,657,658,659,660,661,662,null,664,null,674,null,676,677,678,679,680,681,682,683,684,685,686,687,688,689,690,691,692,693,694,695,696,697,698,699,700,701,702,703,704,705,706,707,708,709,710,711,712,713,714,715,716,null,736,null,753]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\KNOWN-HAZARDS.md"},"limits":{"columnTruncated":{"maxColumn":768}}}},"isError":false,"timestamp":1784247411651}}
{"type":"message","id":"4206d8cb","parentId":"1759c2cf","timestamp":"2026-07-17T00:16:51.678Z","message":{"role":"toolResult","toolCallId":"call_9OtqiNkClcpbFHPdz1QYk7bI|fc_099a7567dadef391016a5974738bc4819b9699dbe56d0f1c23","toolName":"read","content":[{"type":"text","text":"const OPENER_PIN_MAX: usize = 64 * 1024;\n\n/// Restart-durable opener capture (ADR-0038 B, REQ-STREAM-OPENER-DURABLE):\n/// the stream's complete first NDJSON line, pinned OUTSIDE the evictable data\n/// ring and held until the stream row goes away. Recovery classifies from\n/// this fact, never from ring seq 0 (which a bounded ring evicts under\n/// traffic — the abandoned-active-stream leg of the hertz RCA).\nenum OpenerCapture {\n    /// Still hunting the first `\\n` — holds every byte seen so far.\n    Buffering(Vec<u8>),\n    /// The complete first line (newline excluded), immutable from here on.\n    Pinned(Vec<u8>),\n    /// No newline within [`OPENER_PIN_MAX`] — not an NDJSON opener; give up\n    /// bounded (classification reads this as \"no opener\").\n    Oversize,\n}\n\n/// Transport backing a held connection. A real `Quic` connection (the only\n/// kind an inbound handshake can produce), or a broker-minted `Loopback` —\n/// an in-process connection with NO iroh handshake whose identity IS this node,\n/// established BY CONSTRUCTION. A loopback conn can ONLY be minted broker-side\n/// via [`NetHost::dial_loopback`]; it can never arise from an inbound handshake\n/// (a remote always presents its own key and cannot claim to be us). This is\n/// what lets a LOCAL `spt rc` ride the byte-identical `serve_attach` pump (M12\n/// W1.5, B1) — QUIC self-dial is refused by iroh (\"Connecting to ourself is not\n/// supported\"), so local attach rides a broker loopback conn instead.\nenum ConnKind {\n    /// A real QUIC connection — kept alive by ownership here; dropping it closes\n    /// the connection. Cloned (cheap handle) for stream opens.\n    Quic(Connection),\n    /// A broker-minted in-process loopback (no iroh handle, no handshake). Its\n    /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];\n    /// there is no accept loop and no closed-watcher (it lives for the host).\n    Loopback,\n}\n\n/// One live connection the broker holds (QUIC or loopback).\nstruct ConnEntry {\n    /// The transport backing this connection.\n    kind: ConnKind,\n    /// The remote node's stable name (hex pubkey). For QUIC, proven by the\n    /// handshake; for loopback, THIS node's own id (the broker-minted identity).\n    remote_id_hex: String,\n    /// The subnets the peer proved current-epoch seed-knowledge of at connect\n    /// (Mesh-D2, REQ-MESH-1). Empty when seed-proof is disabled (`membership:\n    /// None`). The D5 gate swap reads this in place of `is_trusted(subnet, …)`;\n    /// at D2 it is cached but not yet consumed (the `is_trusted` gates still run\n    /// in parallel — belt + suspenders, no regression).\n    proven_subnets: HashSet<String>,\n}\n\n/// A bounded, sequenced read log for one QUIC stream — the PTY `OutputLog`\n/// discipline applied to the network (D4b): the stream's single read pump is\n/// the only producer; a brain connection attaches as the subscriber; every\n/// subscriber send happens under the enclosing `Mutex`, so live chunks and a\n/// replay can never interleave out of `seq` order. Chunks produced while no\n/// brain is attached sit in the ring and replay on the next subscribe — a\n/// brain restart loses nothing (Spike #3 invariant B/D).\n// [impl->REQ-DAEMON-2]\nstruct StreamLog {\n    stream_id: u64,\n    /// TRANSPORT read counter: bumped once per [`append`](Self::append) (one\n    /// read-pump `read_chunk`), INDEPENDENT of byte size. NOT a logical-chunk\n    /// count — QUIC may coalesce several sender writes into one receiver\n    /// `read_chunk` (or split one), so `next_seq` can be < the number of logical\n    /// chunks the sender issued for the SAME bytes. Correctness (byte-exact +\n    /// exactly-once) holds via these transport seqs + the recv cursor regardless;\n    /// any test asserting `next_seq == logical-chunk-count` is wrong (the Windows\n    /// coalescing flake) and must gate on BYTES instead.\n    next_seq: u64,\n    ring: VecDeque<(u64, Vec<u8>)>,\n    cap_chunks: usize,\n    subscriber: Option<SharedSend>,\n    /// Read side finished cleanly (peer called finish / conn ended).\n    finished: bool,\n    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not\n    /// retentive (ordinary bounded-overwrite stream). A shell-tunnel stream must\n    /// NOT drop bytes — CONTEXT:262 \"tunneled protocols cannot drop frames, so\n    /// congestion surfaces as lag, never loss.\" So a retentive log never cap-evicts\n    /// on append (no loss), and the read pump REFUSES to pull the next chunk once\n    /// the undrained ring reaches this cap (no unbounded growth ⇒ no OOM) — the\n    /// transport's native flow-control then lags the *sender*. A chunk leaves the\n    /// ring ONLY when [`drain`](Self::drain) (the recv cursor) pops it; the pump is\n    /// woken to resume via the entry's `room` notifier. Ordinary streams keep\n    /// `retentive_cap = 0` (a slow/absent brain must not OOM the broker via an\n    /// unbounded ring) and are drained by subscribe, never the sync cursor.\n    retentive_cap: usize,\n    /// The pinned first NDJSON line (ADR-0038 B) — one bounded side fact per\n    /// stream, independent of the ring's eviction. Fed by [`append`](Self::append).\n    opener: OpenerCapture,\n}\n\nimpl StreamLog {\n    fn new(stream_id: u64, cap_chunks: usize) -> Self {\n        StreamLog {\n            stream_id,\n            next_seq: 0,\n            ring: VecDeque::new(),\n            cap_chunks,\n            subscriber: None,\n            finished: false,\n            retentive_cap: 0,\n            opener: OpenerCapture::Buffering(Vec::new()),\n        }\n    }\n\n    /// The pinned opener line, if the first `\\n` has arrived (ADR-0038 B).\n    /// `None` while still buffering or when capture gave up (`Oversize`).\n    fn opener_line(&self) -> Option<Vec<u8>> {\n        match &self.opener {\n            OpenerCapture::Pinned(line) => Some(line.clone()),\n            _ => None,\n        }\n    }\n\n    /// The oldest retained ring seq — the safe subscribe cursor after the ring\n    /// rolled (a `from_seq` below this floor makes the brain's gap check\n    /// reject the resume). `next_seq` when the ring is empty.\n    fn floor_seq(&self) -> u64 {\n        self.ring.front().map(|(s, _)| *s).unwrap_or(self.next_seq)\n    }\n\n    /// Mark this log retentive with backpressure `cap` (chunks). Idempotent set.\n    fn set_retentive(&mut self, cap: usize) {\n        self.retentive_cap = cap;\n    }\n\n    /// Append a freshly-read chunk: assign the next `seq`, retain it in the ring,\n    /// live-send to the current subscriber. Read-pump only. A non-retentive log\n    /// cap-evicts the oldest chunks past `cap_chunks`; a retentive (tunnel) log\n    /// never evicts — bytes leave only via [`drain`](Self::drain).\n    fn append(&mut self, bytes: &[u8]) {\n        // Opener capture (ADR-0038 B): pin the first NDJSON line as it streams\n        // past, before ring bounds can ever evict it. One-shot: `Pinned` and\n        // `Oversize` are terminal states.\n        // [impl->REQ-STREAM-OPENER-DURABLE]\n        if let OpenerCapture::Buffering(buf) = &mut self.opener {\n            match bytes.iter().position(|&b| b == b'\\n') {\n                Some(nl) => {\n                    buf.extend_from_slice(&bytes[..nl]);\n                    let line = std::mem::take(buf);\n                    self.opener = if line.len() > OPENER_PIN_MAX {\n                        OpenerCapture::Oversize\n                    } else {\n                        OpenerCapture::Pinned(line)\n                    };\n                }\n                None => {\n                    buf.extend_from_slice(bytes);\n                    if buf.len() > OPENER_PIN_MAX {\n                        self.opener = OpenerCapture::Oversize;\n                    }\n                }\n            }\n        }\n        let seq = self.next_seq;\n        self.next_seq += 1;\n        self.ring.push_back((seq, bytes.to_vec()));\n        if self.retentive_cap == 0 {\n            while self.ring.len() > self.cap_chunks {\n                self.ring.pop_front();\n            }\n        }\n…\n    }\n…\n}\n…\nstruct StreamEntry {\n…\n    /// Wakes the read pump when a retentive (tunnel) stream's ring drains below\n    /// its backpressure cap (M11-W3, REQ-SHELL-4) — see [`StreamLog::retentive_cap`].\n    /// Unused by ordinary (non-retentive) streams.\n    room: Arc<tokio::sync::Notify>,\n    /// Retired = invisible to dispatch eligibility (ADR-0038 A), NOT torn down:\n    /// retire must never sever the transport — the worker's reply send is\n    /// fire-and-forget, so a retire that dropped this entry could overtake the\n    /// still-queued reply inside the broker and reset the never-finished QUIC\n    /// send half (requester sees torn-EOF, no reply). The reply-flush ordering\n    /// is load-bearing; physical teardown belongs to the conn-close sweep only.\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    retired: std::sync::atomic::AtomicBool,\n}\n\n/// Tables shared between the host's sync surface and its runtime tasks\n/// (accept loop, per-conn stream acceptors, per-stream read pumps).\nstruct NetShared {\n    next_conn_id: AtomicU64,\n    conns: Mutex<HashMap<u64, ConnEntry>>,\n    next_stream_id: AtomicU64,\n    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,\n    ring_cap: usize,\n    /// The conn-liveness event log (D4c) — one per host, all conns interleaved.\n    presence: Mutex<PresenceLog>,\n}\n…\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n…\n        streams\n…\n            .collect()\n    }\n\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\n    /// serve loop ran to completion — its exchange is over, so no future\n    /// dispatcher generation may rediscover and replay it (the control-steal\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n    /// reply sends when it removed the entry (see the `retired` field note).\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\n    /// call newly retired the row (idempotent; absent row = false).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\n        let Some(entry) = self\n            .shared\n            .streams\n            .lock()\n            .unwrap()\n            .get(&stream_id)\n            .map(Arc::clone)\n        else {\n            return false;\n        };\n        let newly = !entry\n            .retired\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\n        if newly {\n            // A retired exchange's request bytes are dead weight — free the\n            // ring; the pinned opener and cursors stay (cheap, bounded).\n            entry.log.lock().unwrap().ring.clear();\n        }\n        newly\n    }\n\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, independent of the evictable data ring. Outer\n    /// `None` = no such stream; inner `None` = no opener pinned (first `\\n`\n    /// not yet arrived, or capture gave up bounded).\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    pub fn stream_opener(&self, stream_id: u64) -> Option<Option<Vec<u8>>> {\n        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\n        // A retired row answers no-such-stream: classification exists only to\n        // serve, and a retired exchange must never be served again.\n        if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {\n            return None;\n        }\n        let line = entry.log.lock().unwrap().opener_line();\n        Some(line)\n    }\n\n    /// Mark a held stream **retentive** with backpressure `cap` chunks (M11-W3,\n    /// REQ-SHELL-4): its read log stops evicting (no byte loss) and its read pump\n    /// lags the sender once the undrained ring reaches `cap` (no unbounded growth).\n    /// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.\n    /// No-op if the stream is gone.\n    // [impl->REQ-SHELL-4]"}],"details":{"displayContent":{"text":"const OPENER_PIN_MAX: usize = 64 * 1024;\n\n/// Restart-durable opener capture (ADR-0038 B, REQ-STREAM-OPENER-DURABLE):\n/// the stream's complete first NDJSON line, pinned OUTSIDE the evictable data\n/// ring and held until the stream row goes away. Recovery classifies from\n/// this fact, never from ring seq 0 (which a bounded ring evicts under\n/// traffic — the abandoned-active-stream leg of the hertz RCA).\nenum OpenerCapture {\n    /// Still hunting the first `\\n` — holds every byte seen so far.\n    Buffering(Vec<u8>),\n    /// The complete first line (newline excluded), immutable from here on.\n    Pinned(Vec<u8>),\n    /// No newline within [`OPENER_PIN_MAX`] — not an NDJSON opener; give up\n    /// bounded (classification reads this as \"no opener\").\n    Oversize,\n}\n\n/// Transport backing a held connection. A real `Quic` connection (the only\n/// kind an inbound handshake can produce), or a broker-minted `Loopback` —\n/// an in-process connection with NO iroh handshake whose identity IS this node,\n/// established BY CONSTRUCTION. A loopback conn can ONLY be minted broker-side\n/// via [`NetHost::dial_loopback`]; it can never arise from an inbound handshake\n/// (a remote always presents its own key and cannot claim to be us). This is\n/// what lets a LOCAL `spt rc` ride the byte-identical `serve_attach` pump (M12\n/// W1.5, B1) — QUIC self-dial is refused by iroh (\"Connecting to ourself is not\n/// supported\"), so local attach rides a broker loopback conn instead.\nenum ConnKind {\n    /// A real QUIC connection — kept alive by ownership here; dropping it closes\n    /// the connection. Cloned (cheap handle) for stream opens.\n    Quic(Connection),\n    /// A broker-minted in-process loopback (no iroh handle, no handshake). Its\n    /// streams are minted eagerly in cross-wired pairs by [`NetHost::open_stream`];\n    /// there is no accept loop and no closed-watcher (it lives for the host).\n    Loopback,\n}\n\n/// One live connection the broker holds (QUIC or loopback).\nstruct ConnEntry {\n    /// The transport backing this connection.\n    kind: ConnKind,\n    /// The remote node's stable name (hex pubkey). For QUIC, proven by the\n    /// handshake; for loopback, THIS node's own id (the broker-minted identity).\n    remote_id_hex: String,\n    /// The subnets the peer proved current-epoch seed-knowledge of at connect\n    /// (Mesh-D2, REQ-MESH-1). Empty when seed-proof is disabled (`membership:\n    /// None`). The D5 gate swap reads this in place of `is_trusted(subnet, …)`;\n    /// at D2 it is cached but not yet consumed (the `is_trusted` gates still run\n    /// in parallel — belt + suspenders, no regression).\n    proven_subnets: HashSet<String>,\n}\n\n/// A bounded, sequenced read log for one QUIC stream — the PTY `OutputLog`\n/// discipline applied to the network (D4b): the stream's single read pump is\n/// the only producer; a brain connection attaches as the subscriber; every\n/// subscriber send happens under the enclosing `Mutex`, so live chunks and a\n/// replay can never interleave out of `seq` order. Chunks produced while no\n/// brain is attached sit in the ring and replay on the next subscribe — a\n/// brain restart loses nothing (Spike #3 invariant B/D).\n// [impl->REQ-DAEMON-2]\nstruct StreamLog {\n    stream_id: u64,\n    /// TRANSPORT read counter: bumped once per [`append`](Self::append) (one\n    /// read-pump `read_chunk`), INDEPENDENT of byte size. NOT a logical-chunk\n    /// count — QUIC may coalesce several sender writes into one receiver\n    /// `read_chunk` (or split one), so `next_seq` can be < the number of logical\n    /// chunks the sender issued for the SAME bytes. Correctness (byte-exact +\n    /// exactly-once) holds via these transport seqs + the recv cursor regardless;\n    /// any test asserting `next_seq == logical-chunk-count` is wrong (the Windows\n    /// coalescing flake) and must gate on BYTES instead.\n    next_seq: u64,\n    ring: VecDeque<(u64, Vec<u8>)>,\n    cap_chunks: usize,\n    subscriber: Option<SharedSend>,\n    /// Read side finished cleanly (peer called finish / conn ended).\n    finished: bool,\n    /// **Retentive backpressure cap** (M11-W3, REQ-SHELL-4), in chunks; `0` ⇒ not\n    /// retentive (ordinary bounded-overwrite stream). A shell-tunnel stream must\n    /// NOT drop bytes — CONTEXT:262 \"tunneled protocols cannot drop frames, so\n    /// congestion surfaces as lag, never loss.\" So a retentive log never cap-evicts\n    /// on append (no loss), and the read pump REFUSES to pull the next chunk once\n    /// the undrained ring reaches this cap (no unbounded growth ⇒ no OOM) — the\n    /// transport's native flow-control then lags the *sender*. A chunk leaves the\n    /// ring ONLY when [`drain`](Self::drain) (the recv cursor) pops it; the pump is\n    /// woken to resume via the entry's `room` notifier. Ordinary streams keep\n    /// `retentive_cap = 0` (a slow/absent brain must not OOM the broker via an\n    /// unbounded ring) and are drained by subscribe, never the sync cursor.\n    retentive_cap: usize,\n    /// The pinned first NDJSON line (ADR-0038 B) — one bounded side fact per\n    /// stream, independent of the ring's eviction. Fed by [`append`](Self::append).\n    opener: OpenerCapture,\n}\n\nimpl StreamLog {\n    fn new(stream_id: u64, cap_chunks: usize) -> Self {\n        StreamLog {\n            stream_id,\n            next_seq: 0,\n            ring: VecDeque::new(),\n            cap_chunks,\n            subscriber: None,\n            finished: false,\n            retentive_cap: 0,\n            opener: OpenerCapture::Buffering(Vec::new()),\n        }\n    }\n\n    /// The pinned opener line, if the first `\\n` has arrived (ADR-0038 B).\n    /// `None` while still buffering or when capture gave up (`Oversize`).\n    fn opener_line(&self) -> Option<Vec<u8>> {\n        match &self.opener {\n            OpenerCapture::Pinned(line) => Some(line.clone()),\n            _ => None,\n        }\n    }\n\n    /// The oldest retained ring seq — the safe subscribe cursor after the ring\n    /// rolled (a `from_seq` below this floor makes the brain's gap check\n    /// reject the resume). `next_seq` when the ring is empty.\n    fn floor_seq(&self) -> u64 {\n        self.ring.front().map(|(s, _)| *s).unwrap_or(self.next_seq)\n    }\n\n    /// Mark this log retentive with backpressure `cap` (chunks). Idempotent set.\n    fn set_retentive(&mut self, cap: usize) {\n        self.retentive_cap = cap;\n    }\n\n    /// Append a freshly-read chunk: assign the next `seq`, retain it in the ring,\n    /// live-send to the current subscriber. Read-pump only. A non-retentive log\n    /// cap-evicts the oldest chunks past `cap_chunks`; a retentive (tunnel) log\n    /// never evicts — bytes leave only via [`drain`](Self::drain).\n    fn append(&mut self, bytes: &[u8]) {\n        // Opener capture (ADR-0038 B): pin the first NDJSON line as it streams\n        // past, before ring bounds can ever evict it. One-shot: `Pinned` and\n        // `Oversize` are terminal states.\n        // [impl->REQ-STREAM-OPENER-DURABLE]\n        if let OpenerCapture::Buffering(buf) = &mut self.opener {\n            match bytes.iter().position(|&b| b == b'\\n') {\n                Some(nl) => {\n                    buf.extend_from_slice(&bytes[..nl]);\n                    let line = std::mem::take(buf);\n                    self.opener = if line.len() > OPENER_PIN_MAX {\n                        OpenerCapture::Oversize\n                    } else {\n                        OpenerCapture::Pinned(line)\n                    };\n                }\n                None => {\n                    buf.extend_from_slice(bytes);\n                    if buf.len() > OPENER_PIN_MAX {\n                        self.opener = OpenerCapture::Oversize;\n                    }\n                }\n            }\n        }\n        let seq = self.next_seq;\n        self.next_seq += 1;\n        self.ring.push_back((seq, bytes.to_vec()));\n        if self.retentive_cap == 0 {\n            while self.ring.len() > self.cap_chunks {\n                self.ring.pop_front();\n            }\n        }\n…\n    }\n…\n}\n…\nstruct StreamEntry {\n…\n    /// Wakes the read pump when a retentive (tunnel) stream's ring drains below\n    /// its backpressure cap (M11-W3, REQ-SHELL-4) — see [`StreamLog::retentive_cap`].\n    /// Unused by ordinary (non-retentive) streams.\n    room: Arc<tokio::sync::Notify>,\n    /// Retired = invisible to dispatch eligibility (ADR-0038 A), NOT torn down:\n    /// retire must never sever the transport — the worker's reply send is\n    /// fire-and-forget, so a retire that dropped this entry could overtake the\n    /// still-queued reply inside the broker and reset the never-finished QUIC\n    /// send half (requester sees torn-EOF, no reply). The reply-flush ordering\n    /// is load-bearing; physical teardown belongs to the conn-close sweep only.\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    retired: std::sync::atomic::AtomicBool,\n}\n\n/// Tables shared between the host's sync surface and its runtime tasks\n/// (accept loop, per-conn stream acceptors, per-stream read pumps).\nstruct NetShared {\n    next_conn_id: AtomicU64,\n    conns: Mutex<HashMap<u64, ConnEntry>>,\n    next_stream_id: AtomicU64,\n    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,\n    ring_cap: usize,\n    /// The conn-liveness event log (D4c) — one per host, all conns interleaved.\n    presence: Mutex<PresenceLog>,\n}\n…\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n…\n        streams\n…\n            .collect()\n    }\n\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\n    /// serve loop ran to completion — its exchange is over, so no future\n    /// dispatcher generation may rediscover and replay it (the control-steal\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n    /// reply sends when it removed the entry (see the `retired` field note).\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\n    /// call newly retired the row (idempotent; absent row = false).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\n        let Some(entry) = self\n            .shared\n            .streams\n            .lock()\n            .unwrap()\n            .get(&stream_id)\n            .map(Arc::clone)\n        else {\n            return false;\n        };\n        let newly = !entry\n            .retired\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\n        if newly {\n            // A retired exchange's request bytes are dead weight — free the\n            // ring; the pinned opener and cursors stay (cheap, bounded).\n            entry.log.lock().unwrap().ring.clear();\n        }\n        newly\n    }\n\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, independent of the evictable data ring. Outer\n    /// `None` = no such stream; inner `None` = no opener pinned (first `\\n`\n    /// not yet arrived, or capture gave up bounded).\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    pub fn stream_opener(&self, stream_id: u64) -> Option<Option<Vec<u8>>> {\n        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\n        // A retired row answers no-such-stream: classification exists only to\n        // serve, and a retired exchange must never be served again.\n        if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {\n            return None;\n        }\n        let line = entry.log.lock().unwrap().opener_line();\n        Some(line)\n    }\n\n    /// Mark a held stream **retentive** with backpressure `cap` chunks (M11-W3,\n    /// REQ-SHELL-4): its read log stops evicting (no byte loss) and its read pump\n    /// lags the sender once the undrained ring reaches `cap` (no unbounded growth).\n    /// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.\n    /// No-op if the stream is gone.\n    // [impl->REQ-SHELL-4]","startLine":120,"lineNumbers":[120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,null,290,null,357,null,522,null,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,null,1531,null,1533,null,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"}}},"isError":false,"timestamp":1784247411678}}
{"type":"message","id":"58a3a693","parentId":"4206d8cb","timestamp":"2026-07-17T00:16:51.702Z","message":{"role":"toolResult","toolCallId":"call_CCrSlQTRMzvqA3pEEy52vDd6|fc_099a7567dadef391016a5974738bcc819ba690ad2840d2fad8","toolName":"grep","content":[{"type":"text","text":"# crates/\n\n## spt-daemon/tests/\n### attach.rs\n 288|\n*289|// [int->REQ-HOST-RUN-1] [int->REQ-RC-1] spt-hosted harness bringup + cross-node\n 290|// attach, end to end: `harnesshost::launch_harness_brokered_in` spawns a harness's\n 291|// `[session.self]` into broker A's PTY (the spt-hosted bringup — the daemon owns\n 292|// the PTY, not an external launcher), then an operator on broker B attaches\n### daemon_e2e.rs\n 10|//! 2. **Psyche-loop** — a [`BrainLifecycle`] (the consolidated re-host of the\n*11|//!    interim `api listen` live half, [`REQ-DAEMON-1`]) spawns the Psyche companion;\n 12|//!    the daemon marks the hosted perch `online`, so its liveness is the daemon\n 13|//!    `status`, never the summarizer pid ([`REQ-HAZARD-DAEMON-HOSTED-LIVENESS`]).\n 14|//! 3. **commune** — a commune drop lands in the watched dir and a scheduled pulse\n*15|//!    tick folds it in (the daemon-hosted lifecycle loop, [`REQ-DAEMON-1`]).\n 16|//! 4. **brain-restart-survives** — the brain is dropped (a crash) and handed off\n 17|//!    repeatedly; across each handoff the child's pid is unchanged, exactly one\n 18|//!    session is hosted, the output stream replays gaplessly, and `gen_start`\n...\n 31|//! must not race a sibling test in this binary.\n*32|// REQ-DAEMON-1 `int` re-pointed to crates/spt-daemon/tests/daemon_lifecycle_real_brain.rs\n 33|// (M11-W0.4 (i)): the REAL brain PROCESS (run_brain) hosts the Psyche, retiring\n 34|// this file's type-level BrainLifecycle::with_config-direct over-claim (it kept\n 35|// green coverage over an UNWIRED production path — the M3-era false-green). This\n### daemon_lifecycle_real_brain.rs\n*1|//! M11-W0.4 (i) production-path int (REQ-DAEMON-1) — re-points the\n*2|//! `[int->REQ-DAEMON-1]` evidence OFF the type-level `daemon_e2e` (which\n 3|//! constructs a [`BrainLifecycle`] directly — green coverage over an UNWIRED\n 4|//! production path, the M3-era false-green) ONTO the actual brain entry point:\n 5|//!\n...\n 16|//! no longer waits for a resident `<id>-psyche` perch to bind (host_one spawns none)\n*17|//! and reaps no detached child. The DAEMON-1 evidence is the hosted pulse loop\n 18|//! ingesting the commune — unchanged and independent of the retired resident spawn.\n 19|//!\n 20|//! Single `#[test]`: it owns the process-global `SPT_HOME`.\n*21|// [int->REQ-DAEMON-1]\n 22|\n 23|use std::sync::Arc;\n 24|use std::thread;\n...\n 88|    // The real brain's pulse loop runs (F-030 W1: NO resident psyche is spawned —\n*89|    // the DAEMON-1 evidence is the hosted loop ticking, not a bound `<id>-psyche`\n 90|    // perch): a commune drop is ingested into the live context tier (proves the loop\n 91|    // ticks in the brain, not a test-driven tick).\n 92|    std::fs::write(\n### dispatch.rs\n 372|        body: \"undriven hello\".to_string(),\n*373|        op_id: \"d91-wan-1\".to_string(),\n 374|    };\n 375|    a.net_stream_send(\n 376|        opened.stream_id,\n### inject_control_wedge.rs\n 2269|    // doyle's forkpty ruling (2026-06-21) + todlando's Windows run + the matrix\n*2270|    // run-1 flake REFRAMED the A2 verdict: the abandoned-remote clear is CONN-CLOSE\n 2271|    // detection, NOT the output path — dropping the operator brain closes its loopback\n 2272|    // QUIC conn (FIN), and a broker that detects the close clears controller_by. That\n 2273|    // clear (when observed) is the CORRECT self-heal of a closed-socket controller\n### net_worker_starve.rs\n 60|/// dial then runs `prove_membership`, which a `membership: None` peer never\n*61|/// answers — the documented mixed-pair deadlock. The dialer opens a proof\n 62|/// control stream and awaits a reply that never comes: the deterministic dead\n 63|/// peer. (Copied from netbroker.rs `proving`.)\n 64|fn proving(identity: Identity) -> NetConfig {\n### netbroker.rs\n 50|/// a dial then runs `prove_membership`, which a `membership: None` peer never\n*51|/// answers — the documented mixed-pair deadlock (the dialer opens a proof\n 52|/// control stream and awaits a reply that never comes). The credential content\n 53|/// is irrelevant; its PRESENCE is what makes the dialer open the stream and\n 54|/// block, giving the deterministic black hole the broker-side bound must cut.\n### pump.rs\n 67|/// dial runs `prove_membership`, which a `membership: None` peer never answers\n*68|/// (the documented mixed-pair deadlock) — the deterministic black hole the\n 69|/// broker-side QUIC-op bound must cut. The credential content is irrelevant; its\n 70|/// PRESENCE is what makes the dialer open the proof control stream and block.\n 71|fn proving() -> NetConfig {\n...\n 665|\n*666|/// [int->REQ-PUMP-PEER-ISOLATION] the load-bearing W2 gate: a MIXED roster — one\n 667|/// LIVE peer (mutually proving → connects) alongside N OFFLINE peers (accept the\n 668|/// handshake but never answer the proof → each dial DIAL_FAILs at the broker\n 669|/// bound). The live peer CONNECTS and is advertised to (its due push legs run) in\n### redispatch.rs\n 548|    let pinned = probe\n*549|        .net_stream_opener(s1_a)\n 550|        .expect(\"opener query answers\");\n 551|    assert!(pinned.is_some(), \"the opener stays pinned across the ring roll\");\n 552|\n### reseed.rs\n 5|//! wired so the grace can run. The acceptance the milestone names: after a seed\n*6|//! rotation, a **benign offliner** that returns on the prior epoch (`N-1`) is\n 7|//! re-seeded over the member control stream and converges to the current\n 8|//! generation — while a **revoked** node, even one that kept its prior-epoch\n 9|//! seed, is denied and never re-admitted (it must re-pair). Same two-host shape\n### resume.rs\n 294|\n*295|// [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION] mixed-mode forward gap (D4-2b,\n 296|// doyle amendment 5): a brain already in resume mode (it resumed ≥1 session) that\n 297|// then spawns NEW sessions must track each per-session. Without the fix, every\n 298|// unmapped session shares the one strict `next_seq` cursor, so the first fresh\n### twohost.rs\n 1086|        body: WAN_BODY.to_string(),\n*1087|        op_id: \"twohost-wan-1\".to_string(),\n 1088|    };\n 1089|    a.net_stream_send(\n 1090|        opened.stream_id,\n\n## spt/tests/\n### brain_survive.rs\n 316|    let (pid1, _gen1, hash1) = wait_ready(&ready_path, Some(pid0), Duration::from_secs(45))\n*317|        .unwrap_or_else(|| teardown_panic(&stop, \"gen-1 brain never respawned onto the swap\"));\n 318|    assert_ne!(pid0, pid1, \"the respawned brain is a fresh process\");\n 319|    assert_eq!(\n 320|        hash1.as_deref(),\n### contract_e2e.rs\n 191|    let id = \"listen-e2e\";\n*192|    let session_id = \"sid-listen-1\";\n 193|    // The anchor must be a live process; the test runner itself qualifies.\n 194|    let anchor = std::process::id().to_string();\n 195|\n### live_bind_firsthost_e2e.rs\n*1|//! M11-W0.3 first-host int (REQ-DAEMON-1) — the spt-hosted (bind, no listen)\n 2|//! mirror of `live_firsthost_e2e`, closing the bind-no-Psyche gap:\n 3|//!\n 4|//! a live-capable `spt api bind <id> --type live_agent` BINDS the Self perch\n...\n 11|//\n*12|// [int->REQ-DAEMON-1]\n 13|\n 14|use std::path::PathBuf;\n 15|use std::process::{Command, Output};\n...\n 135|    // spawns no resident, so there is no `agent8-psyche` nested perch to assert. The\n*136|    // DAEMON-1 evidence here is the brain reconcile HOSTING the bind-established live\n 137|    // agent (set.len()==1 above) and un-hosting it on the offline transition below.\n 138|\n 139|    // ── teardown via the public un-host path (offline transition). No settle: the\n### live_firsthost_e2e.rs\n*1|//! M11-W0.2 first-host int (REQ-DAEMON-1) — the REAL establish-marks-online path,\n 2|//! end-to-end through the actual `spt` binary (no synthetic-online seed like the\n 3|//! W0.1 units):\n 4|//!\n...\n 11|//\n*12|// [int->REQ-DAEMON-1]\n 13|\n 14|use std::path::PathBuf;\n 15|use std::process::{Command, Output};\n### n1_compat.rs\n*1|//! Restoration V6 — N-1 broker/brain argv-compat harness (ADR-0018 D3-4).\n 2|//!\n 3|//! Steady state after every routine update is **new-brain × old-broker** (the\n 4|//! broker almost never updates), so the brain-spawn argv must stay\n...\n 7|//! the first versioned spawn-time field — `--generation` / `--start-reason` —\n*8|//! so this is the first field with an N-1 window to assert.\n 9|//!\n 10|//! **What this scaffold proves now (single binary, both roles):** the current\n 11|//! `spt daemon brain` binary accepts BOTH argv shapes without a clap usage\n...\n 13|//!   - the **bare** old-broker invocation (`daemon brain`, no flags), the exact\n*14|//!     shape an older broker that predates D3-2 would spawn → the brain must\n 15|//!     default (generation 0 / `cold`), never die on an arity/unknown-arg error\n 16|//!     before it can rehydrate (KH-2.3's named failure mode); and\n 17|//!   - the **stamped** new-broker invocation (`daemon brain --generation N\n...\n 25|//!\n*26|//! **How it GROWS to a true gate (D7, V6):** the \"old broker\" becomes a pinned\n 27|//! prior `spt` binary that actually spawns + supervises this new brain over the\n 28|//! socket, and each later additive field (D4–D6) adds one assertion here. This\n 29|//! file is the seam those land on.\n...\n 69|\n*70|/// The N-1 window for D3-2's spawn-time field: the new brain accepts both the\n 71|/// bare old-broker argv (defaults) and the stamped new-broker argv. Neither\n 72|/// shape may clap-reject before the brain reaches its connect loop.\n 73|// [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n...\n 93|            \"bare `daemon brain` (old-broker argv) must parse + reach the connect loop, \\\n*94|             not clap-reject — the new-brain × old-broker N-1 window (KH-2.3)\"\n 95|        );\n 96|    }\n 97|\n### n1_pairing.rs\n*1|//! Restoration D7-2 (ADR-0018 V6) — **new-brain × old-broker** N-1 verb-surface\n 2|//! gate: the steady state after every routine brain-only update is a NEW brain\n*3|//! talking to an OLDER broker (the broker almost never updates), so the current\n 4|//! brain must come up + serve against a prior-era broker over the socket verbs\n 5|//! without a clap/arity rejection or a verb desync (KH-2.3 — \"the single most\n 6|//! update-frequency-sensitive invariant\").\n...\n 17|//! pin is the **restoration split boundary: D1 skeleton `0c95435`** (the first\n*18|//! commit where the broker spawns a brain child). D1 also gives the **widest** N-1\n 19|//! window: its broker spawns its own brain with the *bare* pre-D3-2 argv (no\n 20|//! `--generation` / `--start-reason`), and the current brain accepts both shapes.\n 21|//! **Advance rule:** the pin advances to the latest PUBLISHED split-era release\n 22|//! once one exists — so post-v0.4.0 this gate automatically tests the real fleet\n*23|//! N-1, not a frozen D1 ref.\n 24|//!\n 25|//! ## Pairing mechanics (doyle call 4b — SOCKET VERBS, not supervision custody)\n 26|//!\n*27|//! An old broker's supervisor execs its OWN `current_exe`; it will never\n 28|//! spontaneously spawn the new brain, and bending the D6 record-driven selection\n 29|//! into a test hack would test the wrong thing. So: start the old `spt daemon run`\n 30|//! (a real process — it spawns its own old brain, which is harmless idle noise),\n*31|//! then **test-launch the current-tree `spt daemon brain` against the old broker's\n 32|//! socket/home** and prove it serves the verb surface. The compat surface is the\n 33|//! socket verbs + argv, not who supervises whom.\n 34|//!\n...\n 95|\n*96|    // ── Start the OLD broker (`<pin> daemon run`); it binds the broker socket and\n 97|    //    spawns its OWN old brain, which writes brain.ready once it has connected\n*98|    //    — our signal that the old broker is serving. ──\n 99|    let mut old = Command::new(&old_broker)\n 100|        .no_window()\n 101|        .args([\"daemon\", \"run\"])\n...\n 113|        kill(&mut old);\n*114|        panic!(\"old broker never brought its brain up (no brain.ready) — cannot pair\");\n 115|    }\n 116|\n*117|    // ── Test-launch the CURRENT brain against the OLD broker's socket/home, with\n*118|    //    the stamped (post-D3-2) argv the old broker never emits — the new-brain ×\n 119|    //    old-broker direction. Capture stderr to read its handshake trace. ──\n 120|    let stderr_path = home.path().join(\"current-brain.stderr\");\n 121|    let stderr_file = std::fs::File::create(&stderr_path).expect(\"create stderr capture\");\n...\n 137|\n*138|    // Let it connect + handshake + run several heartbeats against the old broker.\n 139|    thread::sleep(Duration::from_secs(6));\n 140|\n 141|    // (1) No clap/arity reject, no verb desync: the current brain is STILL ALIVE\n...\n 154|        // the steady-state trace land there, not on the inherited handle above.\n*155|        // Read both so the N-1 assertions see the brain's diagnostics wherever they went.\n 156|        let persist = home.path().join(\"logs\").join(\"daemon.stderr.log\");\n 157|        let _ = std::fs::File::open(&persist).and_then(|mut f| f.read_to_string(&mut s));\n 158|        s\n...\n 162|        alive,\n*163|        \"the current brain must stay alive against the old broker (no clap/arity reject, \\\n*164|         no verb desync) — new-brain × old-broker N-1 steady state.\\nTRACE:\\n{trace}\"\n 165|    );\n*166|    // (2) The handshake completed: it connected to the old broker's socket.\n 167|    assert!(\n 168|        trace.contains(\"BRAIN_UP\"),\n*169|        \"the current brain must connect + handshake the old broker (BRAIN_UP).\\nTRACE:\\n{trace}\"\n 170|    );\n 171|    // (3) Stable heartbeats: the net-status verb round-trips against the old\n 172|    //     broker every heartbeat — a broker loss would print BRAIN_BROKER_LOST.\n...\n 174|        !trace.contains(\"BRAIN_BROKER_LOST\"),\n*175|        \"the current brain lost the old broker mid-run — a verb-surface desync.\\nTRACE:\\n{trace}\"\n 176|    );\n 177|}\n### trial_drain_drive_e2e.rs\n 22|//! The fix: `run_brain`'s heartbeat loop issues `Brain::sessions()` every tick, so the\n*23|//! candidate DRIVES the live older broker's already-shipped reap and un-strands its\n 24|//! OWN trial with no coordinated broker restart.\n 25|//!\n 26|//! ## Why the wedge is established AFTER the candidate boots (the resume paradox)\n### worker_lifecycle_e2e.rs\n 5|//! Proves:\n*6|//!  1. N-1 — worker-start MINTS `{parent}-w{N}` and emits the BARE id ALONE on\n 7|//!     STDOUT (the machine-readable result the adapter reads — nothing else on\n 8|//!     stdout); the human `WORKER_STARTED:` line rides STDERR.\n*9|//!  2. N-1 — the per-parent counter is monotonic (a second start → `-w2`).\n 10|//!  3. W-2 — worker-stop authenticates by the PARENT's `session_id` (the adapter's\n 11|//!     natural emission `worker-stop <id> --session-id <parent sid>`), which the OLD\n 12|//!     empty-sid + token-only record refused 100% (the leak root).\n\nShowing files 1-20 of 47+. Use skip=20 for the next page, or narrow paths/pattern."}],"details":{"scopePath":"crates/spt-daemon/tests, crates/spt/tests, crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":53,"fileCount":20,"files":["crates/spt-daemon/tests/attach.rs","crates/spt-daemon/tests/daemon_e2e.rs","crates/spt-daemon/tests/daemon_lifecycle_real_brain.rs","crates/spt-daemon/tests/dispatch.rs","crates/spt-daemon/tests/inject_control_wedge.rs","crates/spt-daemon/tests/net_worker_starve.rs","crates/spt-daemon/tests/netbroker.rs","crates/spt-daemon/tests/pump.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/reseed.rs","crates/spt-daemon/tests/resume.rs","crates/spt-daemon/tests/twohost.rs","crates/spt/tests/brain_survive.rs","crates/spt/tests/contract_e2e.rs","crates/spt/tests/live_bind_firsthost_e2e.rs","crates/spt/tests/live_firsthost_e2e.rs","crates/spt/tests/n1_compat.rs","crates/spt/tests/n1_pairing.rs","crates/spt/tests/trial_drain_drive_e2e.rs","crates/spt/tests/worker_lifecycle_e2e.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/attach.rs","count":1},{"path":"crates/spt-daemon/tests/daemon_e2e.rs","count":3},{"path":"crates/spt-daemon/tests/daemon_lifecycle_real_brain.rs","count":5},{"path":"crates/spt-daemon/tests/dispatch.rs","count":1},{"path":"crates/spt-daemon/tests/inject_control_wedge.rs","count":1},{"path":"crates/spt-daemon/tests/net_worker_starve.rs","count":1},{"path":"crates/spt-daemon/tests/netbroker.rs","count":1},{"path":"crates/spt-daemon/tests/pump.rs","count":2},{"path":"crates/spt-daemon/tests/redispatch.rs","count":1},{"path":"crates/spt-daemon/tests/reseed.rs","count":1},{"path":"crates/spt-daemon/tests/resume.rs","count":1},{"path":"crates/spt-daemon/tests/twohost.rs","count":1},{"path":"crates/spt/tests/brain_survive.rs","count":1},{"path":"crates/spt/tests/contract_e2e.rs","count":1},{"path":"crates/spt/tests/live_bind_firsthost_e2e.rs","count":3},{"path":"crates/spt/tests/live_firsthost_e2e.rs","count":2},{"path":"crates/spt/tests/n1_compat.rs","count":6},{"path":"crates/spt/tests/n1_pairing.rs","count":18},{"path":"crates/spt/tests/trial_drain_drive_e2e.rs","count":1},{"path":"crates/spt/tests/worker_lifecycle_e2e.rs","count":2}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# crates/\n\n## spt-daemon/tests/\n### attach.rs\n 288│\n*289│// [int->REQ-HOST-RUN-1] [int->REQ-RC-1] spt-hosted harness bringup + cross-node\n 290│// attach, end to end: `harnesshost::launch_harness_brokered_in` spawns a harness's\n 291│// `[session.self]` into broker A's PTY (the spt-hosted bringup — the daemon owns\n 292│// the PTY, not an external launcher), then an operator on broker B attaches\n### daemon_e2e.rs\n 10│//! 2. **Psyche-loop** — a [`BrainLifecycle`] (the consolidated re-host of the\n*11│//!    interim `api listen` live half, [`REQ-DAEMON-1`]) spawns the Psyche companion;\n 12│//!    the daemon marks the hosted perch `online`, so its liveness is the daemon\n 13│//!    `status`, never the summarizer pid ([`REQ-HAZARD-DAEMON-HOSTED-LIVENESS`]).\n 14│//! 3. **commune** — a commune drop lands in the watched dir and a scheduled pulse\n*15│//!    tick folds it in (the daemon-hosted lifecycle loop, [`REQ-DAEMON-1`]).\n 16│//! 4. **brain-restart-survives** — the brain is dropped (a crash) and handed off\n 17│//!    repeatedly; across each handoff the child's pid is unchanged, exactly one\n 18│//!    session is hosted, the output stream replays gaplessly, and `gen_start`\n   │...\n 31│//! must not race a sibling test in this binary.\n*32│// REQ-DAEMON-1 `int` re-pointed to crates/spt-daemon/tests/daemon_lifecycle_real_brain.rs\n 33│// (M11-W0.4 (i)): the REAL brain PROCESS (run_brain) hosts the Psyche, retiring\n 34│// this file's type-level BrainLifecycle::with_config-direct over-claim (it kept\n 35│// green coverage over an UNWIRED production path — the M3-era false-green). This\n### daemon_lifecycle_real_brain.rs\n *1│//! M11-W0.4 (i) production-path int (REQ-DAEMON-1) — re-points the\n *2│//! `[int->REQ-DAEMON-1]` evidence OFF the type-level `daemon_e2e` (which\n  3│//! constructs a [`BrainLifecycle`] directly — green coverage over an UNWIRED\n  4│//! production path, the M3-era false-green) ONTO the actual brain entry point:\n  5│//!\n   │...\n 16│//! no longer waits for a resident `<id>-psyche` perch to bind (host_one spawns none)\n*17│//! and reaps no detached child. The DAEMON-1 evidence is the hosted pulse loop\n 18│//! ingesting the commune — unchanged and independent of the retired resident spawn.\n 19│//!\n 20│//! Single `#[test]`: it owns the process-global `SPT_HOME`.\n*21│// [int->REQ-DAEMON-1]\n 22│\n 23│use std::sync::Arc;\n 24│use std::thread;\n   │...\n 88│    // The real brain's pulse loop runs (F-030 W1: NO resident psyche is spawned —\n*89│    // the DAEMON-1 evidence is the hosted loop ticking, not a bound `<id>-psyche`\n 90│    // perch): a commune drop is ingested into the live context tier (proves the loop\n 91│    // ticks in the brain, not a test-driven tick).\n 92│    std::fs::write(\n### dispatch.rs\n 372│        body: \"undriven hello\".to_string(),\n*373│        op_id: \"d91-wan-1\".to_string(),\n 374│    };\n 375│    a.net_stream_send(\n 376│        opened.stream_id,\n### inject_control_wedge.rs\n 2269│    // doyle's forkpty ruling (2026-06-21) + todlando's Windows run + the matrix\n*2270│    // run-1 flake REFRAMED the A2 verdict: the abandoned-remote clear is CONN-CLOSE\n 2271│    // detection, NOT the output path — dropping the operator brain closes its loopback\n 2272│    // QUIC conn (FIN), and a broker that detects the close clears controller_by. That\n 2273│    // clear (when observed) is the CORRECT self-heal of a closed-socket controller\n### net_worker_starve.rs\n 60│/// dial then runs `prove_membership`, which a `membership: None` peer never\n*61│/// answers — the documented mixed-pair deadlock. The dialer opens a proof\n 62│/// control stream and awaits a reply that never comes: the deterministic dead\n 63│/// peer. (Copied from netbroker.rs `proving`.)\n 64│fn proving(identity: Identity) -> NetConfig {\n### netbroker.rs\n 50│/// a dial then runs `prove_membership`, which a `membership: None` peer never\n*51│/// answers — the documented mixed-pair deadlock (the dialer opens a proof\n 52│/// control stream and awaits a reply that never comes). The credential content\n 53│/// is irrelevant; its PRESENCE is what makes the dialer open the stream and\n 54│/// block, giving the deterministic black hole the broker-side bound must cut.\n### pump.rs\n  67│/// dial runs `prove_membership`, which a `membership: None` peer never answers\n *68│/// (the documented mixed-pair deadlock) — the deterministic black hole the\n  69│/// broker-side QUIC-op bound must cut. The credential content is irrelevant; its\n  70│/// PRESENCE is what makes the dialer open the proof control stream and block.\n  71│fn proving() -> NetConfig {\n    │...\n 665│\n*666│/// [int->REQ-PUMP-PEER-ISOLATION] the load-bearing W2 gate: a MIXED roster — one\n 667│/// LIVE peer (mutually proving → connects) alongside N OFFLINE peers (accept the\n 668│/// handshake but never answer the proof → each dial DIAL_FAILs at the broker\n 669│/// bound). The live peer CONNECTS and is advertised to (its due push legs run) in\n### redispatch.rs\n 548│    let pinned = probe\n*549│        .net_stream_opener(s1_a)\n 550│        .expect(\"opener query answers\");\n 551│    assert!(pinned.is_some(), \"the opener stays pinned across the ring roll\");\n 552│\n### reseed.rs\n 5│//! wired so the grace can run. The acceptance the milestone names: after a seed\n*6│//! rotation, a **benign offliner** that returns on the prior epoch (`N-1`) is\n 7│//! re-seeded over the member control stream and converges to the current\n 8│//! generation — while a **revoked** node, even one that kept its prior-epoch\n 9│//! seed, is denied and never re-admitted (it must re-pair). Same two-host shape\n### resume.rs\n 294│\n*295│// [unit->REQ-HAZARD-BROKER-PROCESS-ISOLATION] mixed-mode forward gap (D4-2b,\n 296│// doyle amendment 5): a brain already in resume mode (it resumed ≥1 session) that\n 297│// then spawns NEW sessions must track each per-session. Without the fix, every\n 298│// unmapped session shares the one strict `next_seq` cursor, so the first fresh\n### twohost.rs\n 1086│        body: WAN_BODY.to_string(),\n*1087│        op_id: \"twohost-wan-1\".to_string(),\n 1088│    };\n 1089│    a.net_stream_send(\n 1090│        opened.stream_id,\n\n## spt/tests/\n### brain_survive.rs\n 316│    let (pid1, _gen1, hash1) = wait_ready(&ready_path, Some(pid0), Duration::from_secs(45))\n*317│        .unwrap_or_else(|| teardown_panic(&stop, \"gen-1 brain never respawned onto the swap\"));\n 318│    assert_ne!(pid0, pid1, \"the respawned brain is a fresh process\");\n 319│    assert_eq!(\n 320│        hash1.as_deref(),\n### contract_e2e.rs\n 191│    let id = \"listen-e2e\";\n*192│    let session_id = \"sid-listen-1\";\n 193│    // The anchor must be a live process; the test runner itself qualifies.\n 194│    let anchor = std::process::id().to_string();\n 195│\n### live_bind_firsthost_e2e.rs\n  *1│//! M11-W0.3 first-host int (REQ-DAEMON-1) — the spt-hosted (bind, no listen)\n   2│//! mirror of `live_firsthost_e2e`, closing the bind-no-Psyche gap:\n   3│//!\n   4│//! a live-capable `spt api bind <id> --type live_agent` BINDS the Self perch\n    │...\n  11│//\n *12│// [int->REQ-DAEMON-1]\n  13│\n  14│use std::path::PathBuf;\n  15│use std::process::{Command, Output};\n    │...\n 135│    // spawns no resident, so there is no `agent8-psyche` nested perch to assert. The\n*136│    // DAEMON-1 evidence here is the brain reconcile HOSTING the bind-established live\n 137│    // agent (set.len()==1 above) and un-hosting it on the offline transition below.\n 138│\n 139│    // ── teardown via the public un-host path (offline transition). No settle: the\n### live_firsthost_e2e.rs\n *1│//! M11-W0.2 first-host int (REQ-DAEMON-1) — the REAL establish-marks-online path,\n  2│//! end-to-end through the actual `spt` binary (no synthetic-online seed like the\n  3│//! W0.1 units):\n  4│//!\n   │...\n 11│//\n*12│// [int->REQ-DAEMON-1]\n 13│\n 14│use std::path::PathBuf;\n 15│use std::process::{Command, Output};\n### n1_compat.rs\n *1│//! Restoration V6 — N-1 broker/brain argv-compat harness (ADR-0018 D3-4).\n  2│//!\n  3│//! Steady state after every routine update is **new-brain × old-broker** (the\n  4│//! broker almost never updates), so the brain-spawn argv must stay\n   │...\n  7│//! the first versioned spawn-time field — `--generation` / `--start-reason` —\n *8│//! so this is the first field with an N-1 window to assert.\n  9│//!\n 10│//! **What this scaffold proves now (single binary, both roles):** the current\n 11│//! `spt daemon brain` binary accepts BOTH argv shapes without a clap usage\n   │...\n 13│//!   - the **bare** old-broker invocation (`daemon brain`, no flags), the exact\n*14│//!     shape an older broker that predates D3-2 would spawn → the brain must\n 15│//!     default (generation 0 / `cold`), never die on an arity/unknown-arg error\n 16│//!     before it can rehydrate (KH-2.3's named failure mode); and\n 17│//!   - the **stamped** new-broker invocation (`daemon brain --generation N\n   │...\n 25│//!\n*26│//! **How it GROWS to a true gate (D7, V6):** the \"old broker\" becomes a pinned\n 27│//! prior `spt` binary that actually spawns + supervises this new brain over the\n 28│//! socket, and each later additive field (D4–D6) adds one assertion here. This\n 29│//! file is the seam those land on.\n   │...\n 69│\n*70│/// The N-1 window for D3-2's spawn-time field: the new brain accepts both the\n 71│/// bare old-broker argv (defaults) and the stamped new-broker argv. Neither\n 72│/// shape may clap-reject before the brain reaches its connect loop.\n 73│// [unit->REQ-HAZARD-HANDOFF-ARGV-COMPAT]\n   │...\n 93│            \"bare `daemon brain` (old-broker argv) must parse + reach the connect loop, \\\n*94│             not clap-reject — the new-brain × old-broker N-1 window (KH-2.3)\"\n 95│        );\n 96│    }\n 97│\n### n1_pairing.rs\n  *1│//! Restoration D7-2 (ADR-0018 V6) — **new-brain × old-broker** N-1 verb-surface\n   2│//! gate: the steady state after every routine brain-only update is a NEW brain\n  *3│//! talking to an OLDER broker (the broker almost never updates), so the current\n   4│//! brain must come up + serve against a prior-era broker over the socket verbs\n   5│//! without a clap/arity rejection or a verb desync (KH-2.3 — \"the single most\n   6│//! update-frequency-sensitive invariant\").\n    │...\n  17│//! pin is the **restoration split boundary: D1 skeleton `0c95435`** (the first\n *18│//! commit where the broker spawns a brain child). D1 also gives the **widest** N-1\n  19│//! window: its broker spawns its own brain with the *bare* pre-D3-2 argv (no\n  20│//! `--generation` / `--start-reason`), and the current brain accepts both shapes.\n  21│//! **Advance rule:** the pin advances to the latest PUBLISHED split-era release\n  22│//! once one exists — so post-v0.4.0 this gate automatically tests the real fleet\n *23│//! N-1, not a frozen D1 ref.\n  24│//!\n  25│//! ## Pairing mechanics (doyle call 4b — SOCKET VERBS, not supervision custody)\n  26│//!\n *27│//! An old broker's supervisor execs its OWN `current_exe`; it will never\n  28│//! spontaneously spawn the new brain, and bending the D6 record-driven selection\n  29│//! into a test hack would test the wrong thing. So: start the old `spt daemon run`\n  30│//! (a real process — it spawns its own old brain, which is harmless idle noise),\n *31│//! then **test-launch the current-tree `spt daemon brain` against the old broker's\n  32│//! socket/home** and prove it serves the verb surface. The compat surface is the\n  33│//! socket verbs + argv, not who supervises whom.\n  34│//!\n    │...\n  95│\n *96│    // ── Start the OLD broker (`<pin> daemon run`); it binds the broker socket and\n  97│    //    spawns its OWN old brain, which writes brain.ready once it has connected\n *98│    //    — our signal that the old broker is serving. ──\n  99│    let mut old = Command::new(&old_broker)\n 100│        .no_window()\n 101│        .args([\"daemon\", \"run\"])\n    │...\n 113│        kill(&mut old);\n*114│        panic!(\"old broker never brought its brain up (no brain.ready) — cannot pair\");\n 115│    }\n 116│\n*117│    // ── Test-launch the CURRENT brain against the OLD broker's socket/home, with\n*118│    //    the stamped (post-D3-2) argv the old broker never emits — the new-brain ×\n 119│    //    old-broker direction. Capture stderr to read its handshake trace. ──\n 120│    let stderr_path = home.path().join(\"current-brain.stderr\");\n 121│    let stderr_file = std::fs::File::create(&stderr_path).expect(\"create stderr capture\");\n    │...\n 137│\n*138│    // Let it connect + handshake + run several heartbeats against the old broker.\n 139│    thread::sleep(Duration::from_secs(6));\n 140│\n 141│    // (1) No clap/arity reject, no verb desync: the current brain is STILL ALIVE\n    │...\n 154│        // the steady-state trace land there, not on the inherited handle above.\n*155│        // Read both so the N-1 assertions see the brain's diagnostics wherever they went.\n 156│        let persist = home.path().join(\"logs\").join(\"daemon.stderr.log\");\n 157│        let _ = std::fs::File::open(&persist).and_then(|mut f| f.read_to_string(&mut s));\n 158│        s\n    │...\n 162│        alive,\n*163│        \"the current brain must stay alive against the old broker (no clap/arity reject, \\\n*164│         no verb desync) — new-brain × old-broker N-1 steady state.\\nTRACE:\\n{trace}\"\n 165│    );\n*166│    // (2) The handshake completed: it connected to the old broker's socket.\n 167│    assert!(\n 168│        trace.contains(\"BRAIN_UP\"),\n*169│        \"the current brain must connect + handshake the old broker (BRAIN_UP).\\nTRACE:\\n{trace}\"\n 170│    );\n 171│    // (3) Stable heartbeats: the net-status verb round-trips against the old\n 172│    //     broker every heartbeat — a broker loss would print BRAIN_BROKER_LOST.\n    │...\n 174│        !trace.contains(\"BRAIN_BROKER_LOST\"),\n*175│        \"the current brain lost the old broker mid-run — a verb-surface desync.\\nTRACE:\\n{trace}\"\n 176│    );\n 177│}\n### trial_drain_drive_e2e.rs\n 22│//! The fix: `run_brain`'s heartbeat loop issues `Brain::sessions()` every tick, so the\n*23│//! candidate DRIVES the live older broker's already-shipped reap and un-strands its\n 24│//! OWN trial with no coordinated broker restart.\n 25│//!\n 26│//! ## Why the wedge is established AFTER the candidate boots (the resume paradox)\n### worker_lifecycle_e2e.rs\n  5│//! Proves:\n *6│//!  1. N-1 — worker-start MINTS `{parent}-w{N}` and emits the BARE id ALONE on\n  7│//!     STDOUT (the machine-readable result the adapter reads — nothing else on\n  8│//!     stdout); the human `WORKER_STARTED:` line rides STDERR.\n *9│//!  2. N-1 — the per-parent counter is monotonic (a second start → `-w2`).\n 10│//!  3. W-2 — worker-stop authenticates by the PARENT's `session_id` (the adapter's\n 11│//!     natural emission `worker-stop <id> --session-id <parent sid>`), which the OLD\n 12│//!     empty-sid + token-only record refused 100% (the leak root)."},"isError":false,"timestamp":1784247411702}}
{"type":"message","id":"5005ac8e","parentId":"58a3a693","timestamp":"2026-07-17T00:16:51.716Z","message":{"role":"toolResult","toolCallId":"call_tFGSIrmOB3RnCSIBPv6ATvsu|fc_099a7567dadef391016a5974738bb4819b8fa8da148580d2ae","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## brainproc.rs\n 9|//! `spt daemon brain` child. The child connects over the existing broker socket\n*10|//! IPC, signals `ready`, hosts the brain-side logic loops, and idles. D2\n 11|//! migrates the broker-resident loops here one per commit; **D2-1 moved the net\n 12|//! consumers** (inbound dispatcher + outbound peer pump — pure IPC clients, no\n 13|//! shared `Arc<Broker>`) into the brain, gated on the existing `net-status`\n...\n 33|use crate::brain::{now_ms, Brain};\n*34|use crate::endpoint::{brain_ready_path, broker_socket_name};\n 35|use crate::relcache::{AppliedRecord, ReleaseCache};\n 36|\n 37|/// Supervisor backoff floor after a brain death (mirrors the peer-pump\n...\n 44|/// A run at least this long counts as healthy and resets the backoff floor.\n*45|/// Reused as the per-attempt **trial window** (ADR-0018 Q7, D6-2): a candidate\n*46|/// brain has this long to signal `ready` for its generation before the window\n*47|/// elapses (the \"up but useless\" alive-never-ready rollback path).\n 48|const SUPERVISE_HEALTHY_RUN: Duration = Duration::from_secs(30);\n*49|/// The readiness-trial retry budget (ADR-0018 Q7, D6-2): a candidate that\n*50|/// **exits before** signaling ready is respawned this many consecutive times\n 51|/// (a transient first-boot hiccup should not abandon a good binary) before the\n 52|/// supervisor auto-rolls-back to the last-known-good binary. Matched to the\n*53|/// capped-backoff cadence; either this budget OR an alive-but-never-ready\n 54|/// window-expiry triggers rollback.\n*55|const TRIAL_PREREADY_BUDGET: u32 = 3;\n 56|/// Poll/sleep slice so a `stop` request (and a child exit) land promptly.\n 57|const TICK: Duration = Duration::from_millis(200);\n 58|\n...\n 61|const BRAIN_CONNECT_TRIES: u32 = 300;\n*62|/// Cadence the idle brain refreshes its ready breadcrumb + probes the broker.\n 63|const BRAIN_HEARTBEAT: Duration = Duration::from_millis(500);\n 64|\n 65|/// Why the broker spawned this brain — the Q4 update-vs-crash discriminator the\n...\n 125|    /// Request a planned restart (broker handler side) — idempotent: coalesces\n*126|    /// with any already-pending request into one cycle.\n 127|    pub fn request(&self) {\n 128|        self.requested.store(true, Ordering::SeqCst);\n 129|    }\n...\n 155|///\n*156|/// It inherits the broker's already-unelevated token as a plain child, so it\n 157|/// runs no de-elevation guard of its own.\n 158|// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n 159|pub fn run_brain(generation: u64, reason: StartReason) -> io::Result<()> {\n...\n 192|    // CURSOR-ONLY resume (UPDATE-WEDGE round 3, REQ-BRAIN-RESUME-NO-CONN-DEADLOCK,\n*193|    // doyle Option A-split): the supervised daemon brain is a NON-DRAINING consumer of\n 194|    // this conn (the heartbeat below reads it only in net_status()/sessions()\n*195|    // drain-and-discard loops), so it MUST NOT subscribe — an undrained subscription on\n 196|    // this request/reply conn is the SharedSend self-deadlock that re-wedged v0.30.4\n 197|    // (brain.rs resume_sessions deadlock warning). `resume_session_cursors` re-establishes\n 198|    // the per-session dedup cursors + returns the ids WITHOUT subscribing; the subscribing\n*199|    // `resume_sessions` stays for DRAINING callers (the resume int tests / the forward\n*200|    // live-agent-adapter drained carrier). Empty today (the daemon hosts no PTY sessions).\n 201|    // [impl->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n 202|    match brain.resume_session_cursors() {\n 203|        Ok(ids) if !ids.is_empty() => {\n...\n 212|\n*213|    // Signal READY only now — AFTER session resume (restoration D6-1b, ADR-0018\n*214|    // Q7): \"ready\" is the design's \"re-attached all sessions + resumed loops\",\n 215|    // not merely \"socket connected\". The supervisor's readiness gate (D6-2)\n*216|    // promotes the trial binary on this stamp; promoting on a bare post-connect\n 217|    // breadcrumb would accept a brain that connected but never rebuilt\n 218|    // continuity. Today resume is a near-noop (no daemon-hosted sessions), so\n 219|    // this is forward-correct: the gate is defined on the real readiness\n 220|    // semantic the live-agent adapter inherits.\n*221|    write_ready(generation);\n 222|\n 223|    // Shell wake-watchers (restoration D2-2): the offline half of the shell\n 224|    // online/offline exclusivity — a reconcile loop that runs each offline\n\n# docs/\n## BROKER-BRAIN-SPLIT-RESTORATION.md\n 14|\n*15|**Two audit corrections folded in:** the NetHost is **already broker-owned** (`broker.rs:175` `OnceLock`, per the §B M4-D4a note), so the Q5 net item is near-free, not a migration; the supervise-backoff is wired at `peerloop.rs:805` (M8-D4), confirming Q7's reuse claim.\n 16|\n 17|**Six amendments raised and accepted** — each folded into the relevant section below and marked **[V1]**–**[V6]**.\n 18|\n...\n 24|\n*25|While verifying the v0.3.2 cross-OS update fix end-to-end on the real fleet, the third node (`enlyzeam`, a Windows box hosting the `gerald` ready_agent) was updated:\n 26|\n 27|- `spt update apply` returned `APPLIED:6`.\n 28|- The installed `spt.exe` on disk was the valid 0.3.2 Windows PE (magic `4d5a`, sha matched the published binary), `--version` reported `0.3.2`, `applied.json` = `{version:6}`, backup `spt.exe.old-6` present.\n...\n 55|- ADR-0004 Consequences: *\"A small internal broker **process** exists beneath the daemon — a deliberate, bounded walk-back of 'literally one process,' … guaranteeing endpoint survival across updates.\"*\n*56|- ADR-0004 rejected alternatives include **\"whole-daemon live FD-passing\"** and **\"drain + restart\"** — i.e. the chosen design is specifically *restart the brain, the broker process survives.*\n 57|- `M3a-PLAN.md`, `M3-PLAN.md`, `M3b-PLAN.md` all name \"the broker **process** + IPC (M3b).\"\n 58|- Spikes 01/03/04/05/06 each **PROVEN** with *two separate binaries* (`spt-spikes/spike-01-broker-handoff`): a PTY child + a live QUIC transfer survive 100× brain restarts, gapless and exactly-once. A spike that proves \"survives a brain restart\" presupposes the broker is a separate process.\n 59|\n...\n 78|\n*79|> **Glossary note already applied:** `CONTEXT.md` was sharpened during the session to state explicitly that there is **one broker per machine** (per `SPT_HOME`), *not* one per endpoint — a single broker hosts every endpoint's resources and is present even with zero endpoints online. This corrected a latent \"one broker per endpoint\" misconception and is load-bearing for decision Q2.\n 80|\n 81|---\n 82|\n...\n 106|**Alternatives rejected.**\n*107|- *A thin third supervisor process spawning both broker and brain* — adds a process for no invariant the broker can't already hold.\n 108|- *Brain as parent, spawning the broker* — incoherent: a brain restart would orphan or kill its own broker child, dropping endpoints.\n 109|\n 110|**Operator's challenge that strengthened this.** The operator initially held a \"one broker per endpoint\" model and correctly objected that liveness shouldn't live in something that only exists when an endpoint is online. The code shows one broker *per machine*, always present, so the objection's principle is satisfied and actually reinforces putting liveness in the broker.\n...\n 113|\n*114|**Decision.** `apply` verifies + swaps the binary on disk, then signals the brain to **snapshot and self-exit**. The broker — already the brain's supervisor — observes the exit and **respawns from the executable path (now the new binary)**; the new brain re-attaches.\n 115|\n*116|**Reason.** This reuses primitives that already exist. `apply_brain_only` already models exactly \"snapshot the outgoing brain, drop it — the crash boundary the broker already tolerates — re-attach.\" The broker must already supervise and respawn the brain for crash recovery, so an update is just a *planned* snapshot+exit handled by the existing respawn path, and \"respawn from the executable path\" naturally picks up the swapped binary — which is literally what `applyhost.rs:37` says happens \"at next ...\n 117|\n 118|**Alternatives rejected.**\n 119|- *Broker actively cycles the brain on command* — nearly identical, but puts cycle logic into the layer we want to keep dumb and stable.\n...\n 134|\n*135|**Current state that motivates it.** The pump cadences are already catch-up/idempotent (\"stagger from everything due now,\" `peerloop.rs:304`) and are restart-safe as-is. The pulse loop is **phase-relative** (`lifecycle.rs:486` `sleep_interruptible(pulse_period)`), so its phase resets on restart — this is the loop that needs converting to a disk-anchored deadline. Idempotent catch-up loops need no phase state at all; only phase-significant loops are converted.\n 136|\n 137|**Alternative rejected.** *Snapshot each loop's `Instant`/phase into the handoff message and restore it* — fragile (monotonic clocks don't cross processes), lost on crash, and incompatible with the model-B trigger (no brain→brain message exists).\n 138|\n...\n 141|**Decision.** Apply ADR-0004 §B's rule — *a resource is broker-owned iff a live consumer would lose continuity on a brain restart* — to every component that today shares the in-process `Arc<Broker>`:\n*142|- **Net bring-up + REQ-DAEMON-9 self-heal retry → broker** (the brain must not own net bring-up). *Audit correction:* the NetHost is **already** broker-owned (`broker.rs:175` `OnceLock`, §B M4-D4a), and net bring-up + the self-heal retry already live in the `daemon.rs` entry that *becomes* the broker entry — so this item is **near-free**, not a migration.\n 143|- **Digest hub → broker** for the live projections + subscriber sockets; **the parse that feeds it → brain** (pushes parsed digests over IPC).\n 144|- **Seed lock + liveness → broker** (Q2).\n*145|- **Every other brain→broker call becomes a versioned IPC verb** — in the two-process world there is no shared `Arc`, so each current direct call is a latent IPC verb on the same versioned contract that already carries `SPAWN`/`INPUT`/`NET_*`/`SESSIONS`.\n 146|\n*147|**Accepted consequence.** This enlarges the \"dumb kernel.\" That is already-precedented: ADR-0004:63 accepted exactly this when QUIC ownership moved into the broker — *\"the broker is 'stable' in update cadence, not in narrowness.\"*\n 148|\n 149|**[V6] N-1 compat is scoped milestone work.** Because the broker almost never updates but the brain updates routinely, the **steady state after every routine update is NEW brain × OLD broker.** The `classify` pre-swap handshake (`brain_ipc_version ≥ broker.min_compatible`) gates the *forward* direction, but each direct-call→verb conversion must hold that N-1 window across the *whole* verb surface. The milestone must include a **CI-real compatibility test — old-broker binary × new-brain binary acr...\n 150|\n*151|**Exception — shellwake stays brain-side.** The supervised `wake_command` watcher *children* are an exception to the §B continuity rule: they remain **brain-owned** and are re-reconciled from disk on brain start (the boot sweep at `daemon.rs:206-217` already does orphan cleanup).\n 152|\n 153|**Reason for the exception.** Strict §B would put the watcher children in the broker for continuity, but the operator judged the continuity loss acceptable: updates are rare, the brain re-reconciles watchers on start, and keeping watcher-child *supervision logic* out of the kernel keeps the broker narrower. The only cost is a brief window during a (rare) brain update where an offline shell could miss a wake — explicitly accepted.\n 154|\n...\n 156|\n*157|**Decision.** The broker tracks each session-subscriber's last-delivered cursor. On (re)start the new brain queries the broker for **all** hosted sessions and re-attaches each in **resume** mode. **Output is at-least-once** (the broker resumes from last-*sent*); **input/effects stay exactly-once** via the already-broker-owned `EffectJournal`. The explicit `BrainState` handoff *message* is **retired** for production — the new brain cold-starts and reconstructs continuity by querying the broker.\n 158|\n 159|**[V2] Generation custody.** Retiring `BrainState` would orphan the generation counter (KH 2.4: increments on every start/revive; `gen_start` always `= now()`). Custody moves to the **broker**: it owns the counter, increments it on **every** brain spawn (planned or crash), and hands `{generation, start-reason}` to the brain at spawn time via a **versioned argv/hello field** (KH-2.3 forward-compat, defaulted). Because the broker observes every respawn, this is strictly more reliable than passing it brain-...\n 160|\n...\n 162|- Two concrete gaps exist today: `applyhost.rs:239` re-attaches only `sessions.first()` (one session) and with `from_seq = 0` (re-replays the whole ring → duplicate output).\n*163|- Under the model-B trigger the outgoing brain is gone before the new one starts, so **no brain→brain message can be passed**; continuity must come from the persistent side. The broker already holds the per-session output ring (`broker.rs:73-143`); making it also track the delivered cursor lets a respawned brain resume gaplessly with **no disk writes** (respecting Q4) and **no handoff frame**.\n*164|- At-least-once output matches the *existing* terminal-stream contract: SPIKE-05 already established \"terminal-stream ≠ exactly-once transfer\" (a resize repaint reorders and duplicates the stream anyway). Exactly-once output would require a per-chunk ack protocol for a guarantee the terminal stream does not make; the thing that *must* be exactly-once (injected input/effects) already has the journal.\n 165|\n 166|**Alternative rejected.** *Add brain→broker acks so the broker resumes from last-processed (exactly-once output)* — cost without benefit, given the terminal-stream contract.\n 167|\n...\n 169|\n*170|**Decision.** If the new brain fails to reach a **ready** signal (re-attached all sessions + resumed loops) within a bounded number of boots / a healthy-run window, the broker **rolls back to the last-known-good binary** (the `spt.exe.old-N` backup), **quarantines** the bad version (no auto re-apply/re-fetch), and surfaces a loud consent-style notification. The broker holds **last-known-good + candidate** paths and chooses which to spawn (no file manipulation at failure time). Endpoints survive throughou...\n 171|\n*172|**[V1] Rollback-state-compat invariant (forward invariant, mint this milestone).** Auto-rollback spawns the **old** binary against durable state the **new** brain may already have written. This is safe *today* (a source audit confirmed zero state-migration code exists), but the first release that migrates a durable-state schema would silently break Q7's rollback. The invariant must be minted now, while it is free: **a brain must not irreversibly migrate durable state before ready-promotion** — equivale...\n 173|\n 174|**Reason.**\n*175|- The dangerous case is a *runtime* bug (new brain panics on boot), not the binary land (already rolled back at `applyhost.rs:167-170`) and not IPC mismatch (the `classify` handshake `brain_ipc_version ≥ broker.min_compatible` is checked pre-swap). A naive supervisor would crashloop the new binary: endpoints alive but logic dead — \"up but useless.\"\n*176|- The backup binary is already produced for exactly this, and the readiness+backoff machinery already exists — `peerloop.rs:82-91` has `SUPERVISE_BACKOFF_BASE/CAP` + `SUPERVISE_HEALTHY_RUN` (60 s healthy resets backoff), and `supervise_pump` is wired at `peerloop.rs:805` (M8-D4, verified). A bad update that bricks *logic* should self-heal back to last-good without a human, the same spirit as the boot-race self-heal already shipped.\n 177|- The two-phase record fixes a real existing bug: `applyhost.rs:176` writes `record_applied(version)` + `last-outcome=applied` **before** the brain handoff — i.e. before we know the new brain boots. (This is the optimistic `applied.json={version:6}` observed on `enlyzeam`.)\n 178|\n 179|**Alternative rejected.** *Bounded retry → halt and wait for manual intervention* — safer-feeling but leaves logic dead until a human shows up; inconsistent with the self-healing posture.\n...\n 182|\n*183|**Decision/observation.** The broker spawns the brain as a **child process** (`Command::spawn`) and they communicate over the existing local socket IPC. **No `exec` is required**, so there is no Windows/Unix divergence. ConPTY (Windows) and forkpty (Linux) handles stay broker-side and never cross to the brain. Spikes 01 and 04 already proved the handoff on both OSes.\n 184|\n 185|**Reason.** This is precisely the uniformity ADR-0004:25 sought when it rejected whole-daemon live FD-passing as \"hard and platform-divergent.\" Spawn-plus-socket sidesteps it entirely.\n 186|\n## CONTEXT-MEMORY.md\n 19|\n*20|1. **Default-to-project.** A fragment goes to project context unless it *passes the cross-project test* to be promoted to live. Promotion needs justification; demotion is free. The bias points against the leak.\n 21|2. **`current focus / next steps` is project-scoped** — the single biggest leak category. Route it to project unless the focus genuinely spans projects.\n 22|3. **Live holds a thin project roster, never project depth** — cross-project *awareness* travels; depth stays per-project.\n 23|4. **Commune-source asymmetry** (the structural fix): the live signal is strong only from **Self-authored communes**; **echo communes are project-centric** (they synthesize project-heavy session logs). So **echo communes are project-context-primary and live-context-conservative** — they make few/no live edits. This kills the leak at its source. Provenance is recorded on each block (`Source:`).\n...\n 91|\n*92|pi-agent-memory's **human-in-the-loop approval** (extract → review → approve, max-5-candidates) and **bash-mutation guardrails** fit its \"human curates\" philosophy. SPT's Psyche is **trusted to author its own mind autonomously** — so we adopt pi's *structure* (blocks, INDEX, bounded awareness, provenance, OCC) but **not** its approval gate. Controlled mutation is already covered by the drop-file single-writer rule (KNOWN-HAZARDS 6.4).\n## DEBUG-CONVERGE-PLAN.md\n 82|1. **Reuse the update pull/serve handshake** (`propagate.rs`). The serve leg\n*83|   already answers \"what do you have?\" during a pull. Add a *status-only*\n 84|   query (no fetch) that returns `{channel, applied_version, last_outcome}` for\n 85|   the requester's platform. Cheapest: it rides the existing update wire\n 86|   (`net/update.rs`) and the QUIC-handshake-identity origin rule (KH 7.5) gives\n 87|   the responder's identity for free.\n*88|2. **Registry/presence enrichment.** If a node already publishes its applied\n 89|   version + provenance into a registry row (the rollout metadata carries\n 90|   `git_commit`/version provenance), the coordinator can read convergence from\n*91|   rows it already syncs — no new round-trip. Verify whether\n 92|   `relcache`/registry persists \"applied version\" anywhere queryable; if not,\n 93|   prefer option 1.\n 94|\n## DEBUG-ROLLOUT.md\n 63|- `--build-current` builds and includes the coordinator's current platform.\n*64|- `--artifact <target=path>` adds an already-built artifact for another target,\n 65|  such as `x86_64-pc-windows-msvc` or `x86_64-unknown-linux-gnu`.\n 66|- `--version <u64>` overrides the local debug sequence. Use a higher value when\n 67|  recovering from lost local state.\n## DEFERRED.md\n 11|| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1. **M5 ships presence *resolution* (gossip + most-recently-active API) — its future substrate; the endpoint stays deferred** (user decision 2026-06-04). <!-- [doc->REQ-PRES-1] --> **M5-D6 seam note:** the substrate now exists — the gossiped datum (`Instance.last_active_ms`, riding the registry's epoch lease + replication) and the one...\n*12|| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal. **Note:** distinct from the v1 broker *sensing* user input on a PTY it already holds (spt-hosted) — that is in v1 and is NOT OS-wide input monitoring | Opt-in enhancement after agent-interaction presence proves out |\n 13|| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works. Consent *model* is now designed (CONTEXT §Consent & security gates); **the framework (grant store + escalation + pre-consent flags) ships M5 with this capability id reserved-but-refusing** (user decision 2026-06-04) — the capability lands into it later. Defers with it: the remote-fork arm, cross-node shell spawn, `shell_wake_spawn_anywhere` *behavior* (grant s...\n 14|| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly). **M5 ships the framework with this capability id reserved-but-refusing** (user decision 2026-06-04) | Designed alongside instantiate-anywhere's consent gate |\n 15|| Shell-binary (and harness-binary) sandboxing | Shell concept v1 | A capability toolset bounds what the *agent* can ask, not what the *binary* can do with its OS perms. Baseline stance: running any adapter/shell binary = a disclosed, accepted trust risk for all spt-core users | If/when untrusted third-party shell/harness binaries become common enough to warrant OS-level sandboxing |\n...\n 26|| Trust-row prune verb (`spt subnet prune <node>` or similar): registry GHOST rows now decay (4.10), but the dead identity's TRUST rows persist by design (trust is a user decision) — they cost dead dials every pump tick and clutter status views. BIGNET carries two such rows (09ef831e, both sides) | Ghost-eviction fix (2026-06-06) | Trust mutation = security surface; wants the elevation gate + the M8 noun shapes | M8 CLI nounification |\n*27|| Elevated spt-hosted endpoints — a consented elevation satellite: the broker lives inside the always-unelevated daemon (KH 5.7), so spt-hosted children are unelevated by construction; there is NO silent unelevated→elevated path on either OS (that is UAC/sudo's whole point). An agent needing admin powers is harness-hosted-elevated today (works: TCP listeners + talk-down pipes). The spt-hosted form needs a per-grant CONSENTED elevation: UAC prompt / polkit spawns a small elevated PTY-host satellite ho...\n 28|| ~~AMBIGUOUS render~~ **SHIPPED 2026-06-06** (rode the ghost-eviction fix): resolution refusals render valid copy-paste targets at the wansend boundary (`render_refusal`) — labels preferred, key-prefix on label collision, `subnet:id` for cross-subnet | M7 acceptance run (user 2026-06-06) | Pure render fix at the wansend/CLI boundary; carries into the M8 nounification sweep | DONE |\n 29|| Post-join address seeding: the pairing ceremony holds a live, authenticated connection to the peer, but the peer pumps then re-find each other from scratch via id-only discovery (fresh node ids must propagate to n0 DNS first) — observed live as ~1 min to first `status --nodes` convergence after a join. Seed the pump's address cache from the ceremony connection (both sides) for instant first sync | M7 acceptance run (2026-06-06) | Convergence is correct, just slow on first contact; needs an addr-cache...\n 30|| Clock source priority for TOTP/rendezvous: query a well-known world clock (NTP) FIRST, fall back to local time — machines with skewed local clocks currently derive wrong steps/rendezvous tokens silently | M7 acceptance grill (user 2026-06-06) | Needs an NTP client dep + offset caching + offline fallback semantics; ±1 window covers small skew today | First field report of clock-skew join failures, or M8 polish |\n*31|| CLI nounification (M8 candidate; ratified shape from the 2026-06-06 grill): `spt endpoint` namespace absorbs fork/suspend/wake/shutdown/rename/stop/digest + `access` (per-endpoint store — NOT subnet-scoped) + `description` (ex-`resources` blurb; bare = show, `set` = author); `spt endpoint list [--local\\|--subnet <name>]` = merged listing, default all-subnets grouped by subnet, SELF endpoint pinned distinctly at top (bare `spt endpoint` = this list); agent HOT PATH stays flat (`send`/`ring`/`ready`/`w...\n 32|| ~~Node label for an endpoint-less node (REQ-SUBNET-3 gap)~~ **SHIPPED 2026-06-08** (option 2 — node-level presence datum): a peer with **zero endpoints** rendered as a bare key-prefix in `status --nodes` because `node_label` only rode endpoint `Instance` rows. FIX: a node-LEVEL carrier — `SubnetRegistry.node_labels` (node→label) + a `NodeLabelUpdate` feed record riding the existing registry replication stream as an untagged `RegistryFeedRecord` variant (instance bytes unchanged → mixed-version ...\n*33|| **Instant + never-seen peer hostname via pairing-time capture (REQ-SUBNET-3 follow-up; the deferred \"option 1\")**: the shipped node-level label (option 2) is GOSSIP-ONLY, so a peer's name is learned only from a pump round while it is alive. Two residual gaps remain after the eviction fix below: (a) ~1 gossip cadence to appear after a join (the joiner trust-pins by pubkey with `label=None`; the hostname arrives next pump round — enlyzeam↔kitsubito converged to `ENLYZEAM` after a cadence, fine but no...\n 34|| ~~Subnet-scoped liveness probe (REQ-SUBNET-5 detach gap)~~ **SHIPPED 2026-06-08** (REQ-SUBNET-5 int stage): `subnet status --nodes` showed a DETACHED peer as **online** because liveness fell to `Probe` → a raw transport dial by node-pubkey, which succeeded on the peer's still-bound daemon endpoint (one ALPN `spt-core/net/0` serves all subnets, so the dial was subnet-blind). FIX: a new `ServeProbeRecord` (spt-net `serveprobe`) + daemon `serveprobe` handler/requester ask the peer \"serving subnet X?\", a...\n 35|| **Subnet full-mesh visibility (PLANNED MILESTONE — `SUBNET-MESH-PLAN.md`)**: non-directly-paired members never see each other — a subnet is currently the PAIRING GRAPH, not a mesh (A↔B, C↔B; B offline → A and C invisible; even with B online, B doesn't relay). Root: pairwise TOFU trust (`trust.rs`) + own-rows-only gossip / no transitive relay (`peerloop.rs`, KH 4.10/7.5) + no member roster in the pairing seed transfer. FIX = member-AUTHENTICATED subnet data (seed-MAC and/or per-node signatures...\n 36|| `xtask debug-converge` — automated convergence watcher for debug rollout (the REQ-UPD-6 first-slice item ADR-0016 names): watch every expected debug-pinned reachable lab node until all report the target version applied, else a per-node timeout table (Offline / StagedAwaitingConsent / BlockedByBrokerResources / Rejected{reason}). Activates REQ-UPD-6's `int` stage. Full build plan: `docs/DEBUG-CONVERGE-PLAN.md` | REQ-UPD-6 first slice (2026-06-06) | The first slice ships manual \"Apply and observe\" (`DE...\n*37|| **Origin-source update bootstrap (`spt update fetch` — pull the latest signed release from GitHub into the existing verify→stage→apply pipeline)**: today update DISCOVERY is **peer-only** (REQ-UPD-1: a daemon pulls offers from roster peers via `request_update`; `propagate.rs`/`peerloop.rs`). There is NO origin fetch, so the **first node in a fleet — or any isolated/solo node — cannot get a new release without a peer that already staged it** (the fleet converges by gossip, but only *after* a m...\n 38|| **Test broker socket-bind hardening (CI-flake removal)**: `applyhost` tests' `served_broker` helper does `Broker::bind(name).expect(\"bind broker\")` (`crates/spt-daemon/src/applyhost.rs:293`) where `name` = `spt-daemon-d7b-{pid}-{seq}.sock`. Flakes intermittently on the **kitsubito** self-hosted Linux runner (likely AF_UNIX path collision / stale socket file under the runner's reused workspace — Windows named pipes don't hit it). Cost the v0.3.2 release a full red CI run + a `--failed` rerun (2026-06-...\n 39|| **`spt update fetch` single→set shape-upgrade at same version (REQ-UPD-8 transitional trap)**: `cmd_update_fetch` sets the rollback floor = `staged_version` (the monotonic counter), and the gate is a plain `candidate > floor`. A node that fetched+staged version N as a platform **single** (pre-0.3.2 fetch behavior) can never re-fetch version N as a multi-platform **set** — same counter → `Rollback{current:N, candidate:N}`. Bites the **seed-node bootstrap**: the first node to origin-`fetch` a relea...\n 40|| **On-disk `current_exe` reconcile after auto-rollback (the D6-2 reboot residual)**: readiness-gated auto-rollback (ADR-0018 Q7, restoration D6-2) is **record-driven selection** — after a rollback the broker's supervisor keeps spawning the last-known-good `.old-N` binary, chosen from the durable `RolledBack` record, so the rescue survives a reboot for free **with no file rename at the failure instant** (the brittle path the design rejected). But the bytes on disk at `current_exe` are STILL the quarant...\n 41|| **Quarantine operator override (`spt update apply --force` — the note-i escape hatch)**: D6-2's quarantine guard refuses re-applying a version that auto-rolled-back on this node (the `RolledBack` record is the marker; clears naturally when N+1 stages). But a version whose failure was **environmental** (a transient boot condition, not bad bytes) leaves the node **permanently allergic** to a genuinely-good version with no escape but staging a newer one. FIX = an explicit operator override (`spt update ...\n*42|| **✅ RESOLVED — shipped v0.8.3 (REQ-HAZARD-BROKER-QUIC-DEADLINE, KNOWN-HAZARDS 7.8).** ~~Broker-side bound on brain-waiting QUIC ops (the pump-IPC-deadline B-half)~~: the brain-side deadline (REQ-HAZARD-PUMP-IPC-DEADLINE, KNOWN-HAZARDS 7.6) stops the single-threaded pump wedging when a peer black-holes, by bounding the pump's brain-IPC reads and escalating a TimedOut to a supervised restart. But the ROOT cause is broker-side: the broker's `net_open_stream` / `net_stream_send` / `net_dial` handlers m...\n 43|| **Durable in-daemon alarm scheduler (one-shot deadline machinery — the Q4/V3 deferral)**: the broker/brain restoration (ADR-0018 Q4) fixes the *rule* for one-shot scheduled events — persist the absolute `target-time` at creation; every brain start (update *or* crash) reads it and fires-if-due; **never reset on crash** (a user's \"remind me at 3pm\" is a commitment that must outlive any restart). But it deliberately does **not** build the machinery: the spt-core daemon has **no one-shot consumer today...\n 44|| **Autonomous session-digest freshness + Option-C re-home (M9 ADR-0008 amendment deferral)**: M9 re-founded the digest as an ON-DEMAND projection of normalized session logs (`spt-term::projection`; `spt-daemon::digest::project_endpoint_digest`) — snapshot-pull and the structured-delta-stream CONTRACT ship, deltas driven by pulls / `api digest-entry` pushes. Two pieces deferred, both because they have **no consumer yet** (Shell=M10, GUI/frontend pane, Gateway agent-window — none exist): (a) the **aut...\n 45|| **Two-origin `owl_message` context-tap subtype refinement (M10 ADR-0019 deferral)**: the two-origin merge's owl-message producer (`spt::api::startup` `deliver` closure) taps the live-agent **`emit` chokepoint**, so EVERY delivered frame — including resurfaced **notif** frames and shell-context rows — is recorded as a single coarse `owl_message` context-injection entry (`context_kind:\"owl_message\"`). The data is not wrong (a notif IS injected context), only coarsely labeled; the digest is a glanceab...\n*46|| **W2.5 — attached-presence + Kick-and-attach (M12 picker blue tri-state)**: M12 W2's picker ships online/offline status only; the **blue ■ \"attached\" tri-state** + the **\"Kick `<node>` and attach\"** confirm option were SCHEDULED (operator 2026-06-14, NOT dropped) as a dedicated slice built AFTER W2 lands, then wired back into the picker. Scope: (1) a **broker attach-presence query** — \"who is attached to endpoint E's PTY, by which node/surface\" — derive from the `presence.rs` PresenceLog conn c...\n 47|| **Cross-node shell TUNNEL relay (REQ-SHELL-4 cross-node-on-LAN carry)**: W3 built + proved the opaque byte tunnel **same-node only** (broker-homed `TunnelHub` loopback stream pair + the local control socket; `tunnel_e2e` round-trips `<EVENT`-looking bytes byte-exact). The **cross-node** path does NOT exist: `StreamFamily` (dispatch.rs) has no `Tunnel` variant, `classify_first_line` no tunnel arm, `SHELL_LINK` carries only relink/cmd/drive (no tunnel action), and every tunnel surface (`tunnel_ensure/sen...\n*48|| **Per-connection contiguous-sent cursor — structural guard against a decreasing-floor controller re-take (`REQ-HAZARD-CONTROLLER-WRITER-REORDER` follow-up)**: v0.13.0 P1c fixes the controller-writer reorder while KEEPING handoff's eager subscribe (fix #1 \"drop the subscribe\" was reverted — it's the standalone-resume mechanism), so the decreasing-floor double-take (handoff subscribe@K + serve_attach subscribe@0) is still PRESENT and made safe by: `controller_writer` epoch-gated under `send.lock()` (...\n 49|| **Adapter-manifest-enabled rc VT/mouse mode (`spt rc` scroll vs right-click-paste xor resolution)**: v0.13.0 P1/P1b's Windows `EnableMouseCapture` (for client-originated right-click bracketed paste, 7.18) makes Windows Terminal hand ALL wheel events to rc instead of scrolling its own scrollback; rc forwards the wheel to the harness as SGR reports (7.20) but ONLY when the harness has mouse-reporting on (`enabled && sgr`) — and **Claude Code's REPL never enables mouse tracking** (it has no internal scr...\n*50|| **Always-on endpoint kind + `#`-sigil addressing (ADR-0023; REQ-EP-8 / REQ-EP-9) — the core seam for adapter-bridged group/forum messaging**: a `[always-on]` manifest section declaring a resident, owner-less, addressable endpoint binary the daemon boot-launches + supervises **continuously** (independent of agent liveness, register-triggered, one per `<adapter>[:profile]`), which self-manages its `#`-addressed channel endpoints via the existing `api bind` and may call the already-ungated `endpoint wak...\n## DOCS-STRATEGY.md\n 10|\n*11|1. **Sub-10-minute killer quickstart** — runnable, deterministic, whole-working-thing-first, no placeholders. TTFHW (\"time to first hello world\") is the single most-cited conversion lever (~68% abandon trials over setup time).\n 12|2. **Diátaxis four-mode separation** — tutorial / how-to / reference / explanation, never mixed (mixing is the most-cited cause of confusing docs).\n 13|3. **Deterministic, real, copy-pasteable examples everywhere** — no `<YOUR_VALUE_HERE>` placeholders; real values that run. Serves humans *and* agents.\n 14|4. **Dual-depth agent exports** — `llms.txt` (slim curated index) + `llms-full.txt` (full concatenated export), auto-generated in CI, plus markdown content negotiation (`.md` suffix / `Accept: text/markdown`) which cuts agent token use ~90% vs HTML.\n...\n 86|## Capabilities      (one bullet per vertical, each → its own llms.txt)\n*87|- [Messaging](…/messaging/llms.txt): typed ops + file blobs; send / ring / ready.\n 88|- [Live-agent lifecycle](…/lifecycle/llms.txt): skeleton → live → dormant → suspended.\n 89|- … Terminal · Networking · Harness contract · Instances · Shells · Self-update\n 90|\n...\n 105|   Verify:                      spt --version      # spt 0.1.0 (proto v1)\n*106|## 2. The whole working thing   spt ready alice\n*107|                                spt ready bob\n 108|                                spt send bob --from alice \"hello\"\n 109|## 3. Run it                    spt poll --as bob\n 110|                                # → from=alice op=message body=\"hello\"\n...\n 146|\n*147|Top-level `--help` groups by audience (`AGENT COMMANDS` send/ring/ready/poll · `HARNESS COMMANDS` api … · `NODE COMMANDS` pair/subnet/update). Per-command: **USAGE · FLAGS · EXAMPLES (real values) · LEARN MORE**. Beyond gh: a **`spt <cmd> --help --json`** structured-help mode so dev-agents parse the contract without scraping, plus gh's uniform **`--json <fields>` / `--jq` / `--template`** trio on any list command. The `--json` help export is generated + CI-gated alongside rustdoc and the schema.\n## F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md\n 4|perri field seeds + spawn-fail taxonomy (2026-07-03, 16-part dispatch,\n*5|observed-vs-inferred flagged), doyle code recon of the spawn/ready seam\n 6|(anchors below, verified @b6d8357), perri design-review answers Q1–Q4\n 7|(2026-07-03; all four folded in — see §7).\n 8|\n...\n 23|its SessionStart hook, so `establish_perch` never runs (api/startup.rs:299 is\n*24|the ONLY writer of the `ready` marker), and the endpoint sits `ready=false`\n 25|**forever, silently** — while the operator was told it started.\n 26|\n*27|`ready=false` is already rendered by `spt list`/picker (roster.rs:69 →\n 28|cli.rs:2735/3826) but nothing gates or alarms on it: passive display only. The\n 29|attach path's `await_endpoint_session` (cli.rs:1854, 25 s) gates on broker\n 30|**session existence** — created synchronously at spawn, so it passes instantly\n...\n 41||---|------|----------|-------|\n*42|| 1 | TRUST-WEDGE — CC at workspace-trust dialog, null stdin; `ENDPOINT_RUN_STARTED` printed; ready=false forever | every fresh probe cwd, this session | SILENT |\n 43|| 2 | ACCOUNT-ROOT-MISMATCH — trust seeded in a DIFFERENT config root than the spawn resolves; wedges despite a valid-looking seed | Reavo/Brandon dual-root box (si1 vs b6b probes) | SILENT |\n 44|| 3 | PSYCHE-CRASHLOOP — nested psyche CC dies at trust dialog; wrapper respawned ~3/s, ordinal 5358 in 30 min | F-h; now guarded by adapter v0.13.0 give-up + core C3(b) | SILENT (guarded) |\n 45|| 4 | ADAPTER-UNRESOLVED — no registered adapter hosts the parent binary | seed→listen path, bash parent | LOUD pre-spawn (good shape) |\n...\n 67|The silent class (modes 1, 2, 3, 6) must fail with the same loudness as the\n*68|pre-spawn class (modes 4, 5) already does.\n 69|\n 70|### Half A — bind-deadline honesty (spt-core, harness-agnostic)\n 71|\n 72|After the spawn RPC returns in `cmd_endpoint_run --start`, the CLI **awaits\n*73|bind** — the appearance of the `ready` marker (`resolve_ready_file`, the\n 74|`establish_perch` effect) — bounded by a deadline:\n 75|\n 76|- **Success:** `ENDPOINT_RUN_STARTED:{id}` moves to AFTER the bind is observed\n...\n 82|  picker render the honest state. A later successful bind clears the stamp\n*83|  (`establish_perch` already rewrites state; clearing rides there).\n 84|- Classification probes (all CLI-observable; no daemon change needed to\n*85|  detect): child pid alive? broker `has_session` still true? ready marker\n 86|  present? →\n 87|  - `WEDGED_PRE_BIND` — pid alive, session alive, never bound (modes 1, 2)\n 88|  - `EXITED_PRE_BIND` — child exited before binding (mode 5-shaped recipes\n...\n 90|  - `NO_SESSION` — broker session vanished mid-wait (mode 6 shape)\n*91|- Deadline: **default 60 s** (field-confirmed: real binds land t+5–15 s;\n 92|  every observed bringup exceeding ~20 s was a wedge that never bound — the\n*93|  deadline separates cleanly at ≥30 s; **never configure below 30 s**),\n 94|  overridable per-adapter via a manifest key (the bringup-time contract\n 95|  belongs to the adapter) and per-invocation via `--bind-deadline <secs>`\n 96|  (the per-node escape for cold-broker first-spawn / loaded boxes). Escape\n...\n 131|   via `resolve_program_in_dir` like every other adapter-manifest program.\n*132|3. **Bounded + loud:** runtime capped (30 s); non-zero exit or timeout ABORTS\n 133|   the session spawn with `ENDPOINT_SPAWN_FAIL:{id}: reason=PRE_SPAWN_FAIL:\n 134|   <exit/detail>` on CLI stderr (relayed through the spawn RPC error — this\n*135|   path already reaches the CLI as `ENDPOINT_RUN_FAIL` does today).\n 136|4. **Idempotency contract:** the command must be safe to run on every spawn\n 137|   (documented in MANIFEST.md; the seeder re-asserting an existing trust entry\n 138|   is a no-op).\n...\n 163|  owlery cwd is trust-seeded by the same pre-spawn seam, and the crashloop\n*164|  backstop (C3(b) + adapter give-up) already guards the symptom.\n 165|- No mode-6 work: FIXED on 0.23.0 by F-028 B1/B4 (perri definitive repro).\n 166|  The Half-A deadline token still catches its shape if it ever regresses.\n 167|\n...\n 174|| 3 PSYCHE-CRASHLOOP | Half B (owlery cwd seed) | existing C3(b)/v0.13.0 guard |\n*175|| 4 ADAPTER-UNRESOLVED | — (already good) | existing pre-spawn error |\n*176|| 5 STALE-CLI-CONTRACT | — (already good) | existing / Half A `EXITED_PRE_BIND` |\n 177|| 6 RELAUNCH-NEVER-BINDS | FIXED (F-028 B1/B4, 0.23.0-verified) | Half A `NO_SESSION`/`WEDGED_PRE_BIND` (regression net only) |\n 178|\n 179|## 5. Requirement seeds (register at wave start, `required_stages=[]` until active)\n...\n 206|\n*207|1. **Deadline default — ANSWERED:** 60 s safe with margin (real binds t+5–15 s;\n*208|   every >20 s bringup was a wedge, never a slow success). Floor 30 s; per-node\n 209|   override kept for cold-broker first-spawn / loaded boxes. Folded into §3.\n 210|2. **`pre_spawn` on resume — ANSWERED: YES.** Resume re-checks trust (INT-2:\n 211|   resumed CC died at the trust dialog). Folded into §3 Half B.\n## F-030-PSYCHE-EPHEMERAL-DESIGN.md\n 2|\n*3|Status: **READY FOR BUILD 2026-07-04 — wave called by operator.** Author: doyle.\n 4|Inputs: perri `PSYCHE-EPHEMERAL-PLAN.md` (adapter half, doyle-gated PASS; built on\n 5|parked branch `psyche-ephemeral-parked`, publish held for this core wave), the\n 6|2026-07-04 field case (both directions — v0.13.2 bad-ship + hall-bf multi-subnet\n...\n 25|  ~5 s reconcile tick when it isn't resident.\n*26|- Meanwhile the **per-event driver stack already exists, tested, with ZERO\n 27|  production callers**: `resume_psyche` (spt-live resume.rs:175, mode-validated\n 28|  spawn + nested rebind), `run_psyche_turn` (spt-live turn.rs:72, bounded\n 29|  stdin→stdout turn; its only production caller is the ADR-0013 conflict-merge\n...\n 36|**(a) The adapter cannot HOLD residency.** The resident runner's per-pulse\n*37|liveness poll (`spt ready <id> --once`) hard-refuses on a multi-subnet home\n*38|(`READY_FAIL: … pass --subnet`). The nested psyche process **cannot know its\n 39|subnet/home**: core fills only `base_keys` = `{id}`, `{session_id}`, `{node}`\n 40|(lifecycle.rs:338) plus role-local extras — no `{subnet}`, no `{home}`. On\n 41|hfenduleam (2 subnets since BIGNET joined 7/3) the runner died instantly,\n...\n 48|exit after the seed turn tripped the residency machinery, which tore down the\n*49|endpoint's hosted state — ready marker removed, never re-stamped, every\n 50|force-native gated `leg=cli-gate-not-hosted` **permanently** (field brick,\n 51|operator-hit on hall-bf, contained same-day by v0.13.3).\n 52|\n...\n 61|  image only for a bounded turn — the own-copy machinery loses its reason.\n*62|- Psyche TURNS already work daemon-driven today (`psyche_download`, revival\n 63|  prompt, reconcile merge) — proof the per-event shape is the natural one.\n 64|\n 65|## 2. Field evidence (2026-07-04 — provenance)\n...\n 70|| 2 | hall-bf-psyche boot-churn ~5 s cadence, ordinal 6491+, synthetic `sess-<pid>-<ts>` ids, no real session ever binds; starts exactly at BIGNET join (7/3) | operator box, real home | adapter can't hold residency |\n*71|| 3 | Poll-verb root repro: `spt ready <id> --once` on 2-subnet home → exit 1, `READY_FAIL … pass --subnet`, empty stdout | perri, clean repro | adapter can't hold residency |\n*72|| 4 | v0.13.2 shim-exit → residency teardown → ready removed → permanent `leg=cli-gate-not-hosted` | hall-bf + perri wake-confirm | residency expectation destructive |\n*73|| 5 | Psyche turns land fine via daemon-driven paths while the resident churns | hall-bf digest (`psyche_download` at every boundary) | per-event shape already proven |\n 74|| 6 | ~12/min re-host cadence NEVER tripped the C3(b) thrash guard (boot records are apparently not ledger boundaries to the guard) | hall-bf | guard blindness (rides this wave) |\n 75|\n 76|## 3. Design\n...\n 101|budget/cooldown shape): N consecutive turn failures → stamp\n*102|`psyche_host_error` (kept name; it is already the harness-reachable pattern) +\n 103|cooldown, reset on a successful turn. No respawn storm exists anymore because\n 104|there is nothing resident to respawn.\n 105|\n...\n 115|- `resume_psyche(ResumeMode::…)` validates the custody key before spawn\n*116|  (already implemented — resume.rs:183).\n 117|- **Reseed path:** when the psyche's own session is lost/invalid (first host,\n 118|  store wipe, harness-side expiry), fall back to\n 119|  a fresh-with-preload turn — `download_psyche_context` composes\n...\n 136|- **The teardown that made direction (b) destructive is deleted**: no psyche\n*137|  state may ever remove the PARENT endpoint's ready marker or hosted state.\n 138|  Psyche trouble stamps psyche fields only. (This is the conformance test's\n 139|  core assertion.)\n 140|- C3(b) guard rework rides here (evidence #6): the failure budget counts\n...\n 149|**nested `{parent}/{nested}` ids resolve under the parent's home** —\n*150|subnet-less resolution for nested perches (`resolve_perch_path`/ready-verb\n 151|family). Additionally expose a single `{subnet}` base_keys fill **when\n 152|known** (lifecycle.rs:338; absent → LOUD missing-key fail, the `{node}`\n 153|precedent on :344). **There is deliberately NO `{home}` key** (doyle W4 Q1: the\n*154|endpoint home subnet is one concept, and `home:` is already the subnet-name\n 155|qualifier position — a `{home}` template key would only invite meaning-drift).\n 156|Red-first = evidence #3's exact repro: 2-subnet home, nested-id verb, must\n 157|succeed.\n...\n 195|  rig, not separately fixed — the ephemeral model should erase it; if a\n*196|  residue survives, it becomes its own seed with the rig already in hand.\n 197|\n 198|## 4. Coverage matrix\n 199|\n...\n 225|  0.13.2): psyche failure of ANY shape must not remove/alter the parent\n*226|  endpoint's ready/hosted state, and hosting must not churn-respawn.\n 227|  Conformance int = the hall-bf shape: multi-subnet home, live endpoint,\n 228|  failing psyche → parent stays deliverable, no rehost churn, error stamped.\n 229|- `REQ-HAZARD-THRASH-GUARD-BLIND` — hazard (paid-for, evidence #6): the\n...\n 254|   timeouts; the pulse-fired turn needs a config default — propose the\n*255|   existing pulse-period-derived bound, floor 30 s).\n 256|2. Does the checkpoint/commune choreography want a custody-transition turn at\n 257|   parent boundary (psyche notices the parent reset), or stay purely\n 258|   event-driven? (Design assumes purely event-driven; cheap to add later.)\n 259|3. `{subnet}` fill naming — RESOLVED (doyle W4 Q1): a single `{subnet}` key,\n*260|   no `{home}` key (the endpoint home subnet is one concept; `home:` is already\n 261|   the subnet-name qualifier position). Confirmed against CONTEXT.md glossary.\n## F-035-CONFLICT.md\n 3|**Raised by:** todlando, mid-build, 2026-07-09. **Build HELD** (WIP uncommitted on\n*4|`f-035-window-drain-honor`). doyle's COLLAPSE ruling collides with a **shipped**\n 5|acceptance gate my RCA (docs/F-035-RCA.md) missed. Need an A/B/C ruling before I proceed.\n 6|\n 7|---\n...\n 13|\n*14|**`crates/spt/tests/idle_edge_drain_e2e.rs:89`** — `fn spool_while_active_then_idle_fires_injection`\n*15|`// [int->REQ-MSG-IDLE-EDGE-DRAIN] [int->REQ-HAZARD-DELIVERY-STARVATION]`\n 16|\n 17|- **Setup:** spools `spool_message_deferred_at(perch, \"peer\", …)` — which is\n 18|  `insert_message(…, WINDOW_ACTIVE_ONLY, …)` (spool.rs:363). So the row is\n...\n 26|\n*27|`REQ-MSG-IDLE-EDGE-DRAIN` (F-023 leg 2) and `REQ-SEND-WINDOW-DRAIN-HONOR` (F-035) are\n 28|directly incompatible for the relay-less spt-hosted class. The active/idle asymmetry I\n 29|called a bug in the RCA is **intentional** for that class.\n 30|\n...\n 35|For an **spt-hosted** endpoint (broker owns the PTY, `controllable`, relay-less), the\n*36|idle-edge/parked drain is the **ONLY** carrier for an `active_only` row. Traced every path:\n 37|\n 38|| carrier | `active_only` to spt-hosted? |\n 39||---|---|\n...\n 41|| Send-time idle re-offer (`cli.rs:5734`) | **NO** — gated `&& !active_only`. |\n*42|| Hook poll `drain_active_window` (`api poll`) | **N/A** — that is the HARNESS-hosted hook channel; an spt-hosted endpoint has no `api poll` harness. |\n*43|| **Idle-edge / parked drain `drain_idle_spool`** | **YES** — the F-023 leg-2 behavior (`include_deferred = !resting`). **This is the only carrier.** |\n 44|\n 45|So: `active_only` is designed as *\"the active **hook** window\"* — meant for\n 46|**harness-hosted** endpoints that `api poll`. An **spt-hosted** endpoint has no hook, so\n...\n 59|  `active_only`+spt-hosted is knowingly revoked (that class starves, or gets an\n*60|  evaporate/refuse-at-send policy instead). Requires re-scoping `REQ-MSG-IDLE-EDGE-DRAIN`\n 61|  + rewriting its int.\n 62|- **(B) never-starve wins** — the idle edge *should* carry `active_only` to spt-hosted\n 63|  (F-023 correct). Then **F-035 is mis-scoped**: *\"never wakes\"* is the **harness** hook\n...\n 65|  never a contract for the hook-less spt-hosted class. **RETIRE or NARROW\n*66|  REQ-SEND-WINDOW-DRAIN-HONOR; no code change.**\n 67|- **(C) class split** — spt-hosted delivers `active_only` at idle **without waking**\n 68|  (non-interrupting background inject) vs harness-hosted holds-for-poll. New behavior,\n 69|  larger; needs a \"deliver-but-don't-wake\" inject mode the translation binary honors.\n...\n 71|**todlando lean:** the carrier table points at **(B)** — the field-doc *\"never wakes an\n*72|idle target\"* (cli.rs:85) describes the harness hook model, and F-023 already reasoned\n 73|through the spt-hosted no-hook gap deliberately. The original F-035 *symptom* (a\n 74|`--deferred` background row reaching an idle spt-hosted agent) may be **working as\n 75|designed** — it is the only way that class can ever be delivered. If the true concern is\n*76|*\"it interrupts,\"* that is **(C)**, not the drain-exclusion of **(A)**. But this is your call.\n 77|\n 78|My WIP (spool.rs window-axis collapse + inject.rs + 2 units) is staged **uncommitted** — I\n 79|touched neither delivery.rs nor worker.rs. Standing by for A / B / C.\n## F-035-RCA.md\n*1|<!-- [doc->REQ-SEND-WINDOW-DRAIN-HONOR] F-035 RCA — report-before-fix. -->\n*2|# F-035 RCA — idle-edge parked-drain ignores the delivery-window tag\n 3|\n 4|**Status:** RCA-first, **report before fixing** (doyle-dispatched 2026-07-09).\n*5|**REQ:** `REQ-SEND-WINDOW-DRAIN-HONOR` (registered, `required_stages = []` — activate on doyle's fix ruling).\n 6|**Investigator:** todlando. **Off:** `origin/main@2c05dc9` (v0.30.2).\n 7|\n 8|---\n...\n 20|\n*21|For an **spt-hosted, relay-less** endpoint (the class the idle-edge drain targets),\n 22|such a row is delivered through the translation binary at the idle window — the\n 23|exact delivery the tag forbids.\n 24|\n...\n 42|\n*43|### ACTIVE drain — honors the axis (correct)\n 44|\n*45|`spool::drain_active_window_inner`:\n 46|\n 47|```\n 48|include_deferred ? \"WHERE delivered=0 AND \\\"window\\\" != 'idle_only'\"\n...\n 53|\n\n[Showing lines 1-493 of 767 (50.0KB limit). Use :494 to continue. Some lines truncated to 512 chars]"}],"details":{"scopePath":"crates/spt-daemon/src/brainproc.rs, docs, traceable-reqs.toml","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":177,"fileCount":20,"files":["crates/spt-daemon/src/brainproc.rs","docs/BROKER-BRAIN-SPLIT-RESTORATION.md","docs/CONTEXT-MEMORY.md","docs/DEBUG-CONVERGE-PLAN.md","docs/DEBUG-ROLLOUT.md","docs/DEFERRED.md","docs/DOCS-STRATEGY.md","docs/F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md","docs/F-030-PSYCHE-EPHEMERAL-DESIGN.md","docs/F-035-CONFLICT.md","docs/F-035-RCA.md","docs/F-035-RELAY-RCA.md","docs/FAULT-MATRIX.md","docs/FLAKE-LEDGER.md","docs/GATEWAY-LIVENESS-DISPATCH.md","docs/INJECT-SETTLE-REARM-GATE-VERDICT.md","docs/KNOWN-HAZARDS.md","docs/MANIFEST.md","docs/MSG-IDENTITY-DISPATCH.md","docs/MUSL-W1-DISPATCH-RULING.md"],"fileMatches":[{"path":"crates/spt-daemon/src/brainproc.rs","count":20},{"path":"docs/BROKER-BRAIN-SPLIT-RESTORATION.md","count":20},{"path":"docs/CONTEXT-MEMORY.md","count":2},{"path":"docs/DEBUG-CONVERGE-PLAN.md","count":3},{"path":"docs/DEBUG-ROLLOUT.md","count":1},{"path":"docs/DEFERRED.md","count":9},{"path":"docs/DOCS-STRATEGY.md","count":5},{"path":"docs/F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md","count":17},{"path":"docs/F-030-PSYCHE-EPHEMERAL-DESIGN.md","count":18},{"path":"docs/F-035-CONFLICT.md","count":11},{"path":"docs/F-035-RCA.md","count":18},{"path":"docs/F-035-RELAY-RCA.md","count":11},{"path":"docs/FAULT-MATRIX.md","count":4},{"path":"docs/FLAKE-LEDGER.md","count":3},{"path":"docs/GATEWAY-LIVENESS-DISPATCH.md","count":2},{"path":"docs/INJECT-SETTLE-REARM-GATE-VERDICT.md","count":1},{"path":"docs/KNOWN-HAZARDS.md","count":20},{"path":"docs/MANIFEST.md","count":7},{"path":"docs/MSG-IDENTITY-DISPATCH.md","count":4},{"path":"docs/MUSL-W1-DISPATCH-RULING.md","count":1}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/src/\n## brainproc.rs\n   9│//! `spt daemon brain` child. The child connects over the existing broker socket\n *10│//! IPC, signals `ready`, hosts the brain-side logic loops, and idles. D2\n  11│//! migrates the broker-resident loops here one per commit; **D2-1 moved the net\n  12│//! consumers** (inbound dispatcher + outbound peer pump — pure IPC clients, no\n  13│//! shared `Arc<Broker>`) into the brain, gated on the existing `net-status`\n    │...\n  33│use crate::brain::{now_ms, Brain};\n *34│use crate::endpoint::{brain_ready_path, broker_socket_name};\n  35│use crate::relcache::{AppliedRecord, ReleaseCache};\n  36│\n  37│/// Supervisor backoff floor after a brain death (mirrors the peer-pump\n    │...\n  44│/// A run at least this long counts as healthy and resets the backoff floor.\n *45│/// Reused as the per-attempt **trial window** (ADR-0018 Q7, D6-2): a candidate\n *46│/// brain has this long to signal `ready` for its generation before the window\n *47│/// elapses (the \"up but useless\" alive-never-ready rollback path).\n  48│const SUPERVISE_HEALTHY_RUN: Duration = Duration::from_secs(30);\n *49│/// The readiness-trial retry budget (ADR-0018 Q7, D6-2): a candidate that\n *50│/// **exits before** signaling ready is respawned this many consecutive times\n  51│/// (a transient first-boot hiccup should not abandon a good binary) before the\n  52│/// supervisor auto-rolls-back to the last-known-good binary. Matched to the\n *53│/// capped-backoff cadence; either this budget OR an alive-but-never-ready\n  54│/// window-expiry triggers rollback.\n *55│const TRIAL_PREREADY_BUDGET: u32 = 3;\n  56│/// Poll/sleep slice so a `stop` request (and a child exit) land promptly.\n  57│const TICK: Duration = Duration::from_millis(200);\n  58│\n    │...\n  61│const BRAIN_CONNECT_TRIES: u32 = 300;\n *62│/// Cadence the idle brain refreshes its ready breadcrumb + probes the broker.\n  63│const BRAIN_HEARTBEAT: Duration = Duration::from_millis(500);\n  64│\n  65│/// Why the broker spawned this brain — the Q4 update-vs-crash discriminator the\n    │...\n 125│    /// Request a planned restart (broker handler side) — idempotent: coalesces\n*126│    /// with any already-pending request into one cycle.\n 127│    pub fn request(&self) {\n 128│        self.requested.store(true, Ordering::SeqCst);\n 129│    }\n    │...\n 155│///\n*156│/// It inherits the broker's already-unelevated token as a plain child, so it\n 157│/// runs no de-elevation guard of its own.\n 158│// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n 159│pub fn run_brain(generation: u64, reason: StartReason) -> io::Result<()> {\n    │...\n 192│    // CURSOR-ONLY resume (UPDATE-WEDGE round 3, REQ-BRAIN-RESUME-NO-CONN-DEADLOCK,\n*193│    // doyle Option A-split): the supervised daemon brain is a NON-DRAINING consumer of\n 194│    // this conn (the heartbeat below reads it only in net_status()/sessions()\n*195│    // drain-and-discard loops), so it MUST NOT subscribe — an undrained subscription on\n 196│    // this request/reply conn is the SharedSend self-deadlock that re-wedged v0.30.4\n 197│    // (brain.rs resume_sessions deadlock warning). `resume_session_cursors` re-establishes\n 198│    // the per-session dedup cursors + returns the ids WITHOUT subscribing; the subscribing\n*199│    // `resume_sessions` stays for DRAINING callers (the resume int tests / the forward\n*200│    // live-agent-adapter drained carrier). Empty today (the daemon hosts no PTY sessions).\n 201│    // [impl->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n 202│    match brain.resume_session_cursors() {\n 203│        Ok(ids) if !ids.is_empty() => {\n    │...\n 212│\n*213│    // Signal READY only now — AFTER session resume (restoration D6-1b, ADR-0018\n*214│    // Q7): \"ready\" is the design's \"re-attached all sessions + resumed loops\",\n 215│    // not merely \"socket connected\". The supervisor's readiness gate (D6-2)\n*216│    // promotes the trial binary on this stamp; promoting on a bare post-connect\n 217│    // breadcrumb would accept a brain that connected but never rebuilt\n 218│    // continuity. Today resume is a near-noop (no daemon-hosted sessions), so\n 219│    // this is forward-correct: the gate is defined on the real readiness\n 220│    // semantic the live-agent adapter inherits.\n*221│    write_ready(generation);\n 222│\n 223│    // Shell wake-watchers (restoration D2-2): the offline half of the shell\n 224│    // online/offline exclusivity — a reconcile loop that runs each offline\n\n# docs/\n## BROKER-BRAIN-SPLIT-RESTORATION.md\n  14│\n *15│**Two audit corrections folded in:** the NetHost is **already broker-owned** (`broker.rs:175` `OnceLock`, per the §B M4-D4a note), so the Q5 net item is near-free, not a migration; the supervise-backoff is wired at `peerloop.rs:805` (M8-D4), confirming Q7's reuse claim.\n  16│\n  17│**Six amendments raised and accepted** — each folded into the relevant section below and marked **[V1]**–**[V6]**.\n  18│\n    │...\n  24│\n *25│While verifying the v0.3.2 cross-OS update fix end-to-end on the real fleet, the third node (`enlyzeam`, a Windows box hosting the `gerald` ready_agent) was updated:\n  26│\n  27│- `spt update apply` returned `APPLIED:6`.\n  28│- The installed `spt.exe` on disk was the valid 0.3.2 Windows PE (magic `4d5a`, sha matched the published binary), `--version` reported `0.3.2`, `applied.json` = `{version:6}`, backup `spt.exe.old-6` present.\n    │...\n  55│- ADR-0004 Consequences: *\"A small internal broker **process** exists beneath the daemon — a deliberate, bounded walk-back of 'literally one process,' … guaranteeing endpoint survival across updates.\"*\n *56│- ADR-0004 rejected alternatives include **\"whole-daemon live FD-passing\"** and **\"drain + restart\"** — i.e. the chosen design is specifically *restart the brain, the broker process survives.*\n  57│- `M3a-PLAN.md`, `M3-PLAN.md`, `M3b-PLAN.md` all name \"the broker **process** + IPC (M3b).\"\n  58│- Spikes 01/03/04/05/06 each **PROVEN** with *two separate binaries* (`spt-spikes/spike-01-broker-handoff`): a PTY child + a live QUIC transfer survive 100× brain restarts, gapless and exactly-once. A spike that proves \"survives a brain restart\" presupposes the broker is a separate process.\n  59│\n    │...\n  78│\n *79│> **Glossary note already applied:** `CONTEXT.md` was sharpened during the session to state explicitly that there is **one broker per machine** (per `SPT_HOME`), *not* one per endpoint — a single broker hosts every endpoint's resources and is present even with zero endpoints online. This corrected a latent \"one broker per endpoint\" misconception and is load-bearing for decision Q2.\n  80│\n  81│---\n  82│\n    │...\n 106│**Alternatives rejected.**\n*107│- *A thin third supervisor process spawning both broker and brain* — adds a process for no invariant the broker can't already hold.\n 108│- *Brain as parent, spawning the broker* — incoherent: a brain restart would orphan or kill its own broker child, dropping endpoints.\n 109│\n 110│**Operator's challenge that strengthened this.** The operator initially held a \"one broker per endpoint\" model and correctly objected that liveness shouldn't live in something that only exists when an endpoint is online. The code shows one broker *per machine*, always present, so the objection's principle is satisfied and actually reinforces putting liveness in the broker.\n    │...\n 113│\n*114│**Decision.** `apply` verifies + swaps the binary on disk, then signals the brain to **snapshot and self-exit**. The broker — already the brain's supervisor — observes the exit and **respawns from the executable path (now the new binary)**; the new brain re-attaches.\n 115│\n*116│**Reason.** This reuses primitives that already exist. `apply_brain_only` already models exactly \"snapshot the outgoing brain, drop it — the crash boundary the broker already tolerates — re-attach.\" The broker must already supervise and respawn the brain for crash recovery, so an update is just a *planned* snapshot+exit handled by the existing respawn path, and \"respawn from the executable path\" naturally picks up the swapped binary — which is literally what `applyhost.rs:37` says happens \"at next ...\n 117│\n 118│**Alternatives rejected.**\n 119│- *Broker actively cycles the brain on command* — nearly identical, but puts cycle logic into the layer we want to keep dumb and stable.\n    │...\n 134│\n*135│**Current state that motivates it.** The pump cadences are already catch-up/idempotent (\"stagger from everything due now,\" `peerloop.rs:304`) and are restart-safe as-is. The pulse loop is **phase-relative** (`lifecycle.rs:486` `sleep_interruptible(pulse_period)`), so its phase resets on restart — this is the loop that needs converting to a disk-anchored deadline. Idempotent catch-up loops need no phase state at all; only phase-significant loops are converted.\n 136│\n 137│**Alternative rejected.** *Snapshot each loop's `Instant`/phase into the handoff message and restore it* — fragile (monotonic clocks don't cross processes), lost on crash, and incompatible with the model-B trigger (no brain→brain message exists).\n 138│\n    │...\n 141│**Decision.** Apply ADR-0004 §B's rule — *a resource is broker-owned iff a live consumer would lose continuity on a brain restart* — to every component that today shares the in-process `Arc<Broker>`:\n*142│- **Net bring-up + REQ-DAEMON-9 self-heal retry → broker** (the brain must not own net bring-up). *Audit correction:* the NetHost is **already** broker-owned (`broker.rs:175` `OnceLock`, §B M4-D4a), and net bring-up + the self-heal retry already live in the `daemon.rs` entry that *becomes* the broker entry — so this item is **near-free**, not a migration.\n 143│- **Digest hub → broker** for the live projections + subscriber sockets; **the parse that feeds it → brain** (pushes parsed digests over IPC).\n 144│- **Seed lock + liveness → broker** (Q2).\n*145│- **Every other brain→broker call becomes a versioned IPC verb** — in the two-process world there is no shared `Arc`, so each current direct call is a latent IPC verb on the same versioned contract that already carries `SPAWN`/`INPUT`/`NET_*`/`SESSIONS`.\n 146│\n*147│**Accepted consequence.** This enlarges the \"dumb kernel.\" That is already-precedented: ADR-0004:63 accepted exactly this when QUIC ownership moved into the broker — *\"the broker is 'stable' in update cadence, not in narrowness.\"*\n 148│\n 149│**[V6] N-1 compat is scoped milestone work.** Because the broker almost never updates but the brain updates routinely, the **steady state after every routine update is NEW brain × OLD broker.** The `classify` pre-swap handshake (`brain_ipc_version ≥ broker.min_compatible`) gates the *forward* direction, but each direct-call→verb conversion must hold that N-1 window across the *whole* verb surface. The milestone must include a **CI-real compatibility test — old-broker binary × new-brain binary acr...\n 150│\n*151│**Exception — shellwake stays brain-side.** The supervised `wake_command` watcher *children* are an exception to the §B continuity rule: they remain **brain-owned** and are re-reconciled from disk on brain start (the boot sweep at `daemon.rs:206-217` already does orphan cleanup).\n 152│\n 153│**Reason for the exception.** Strict §B would put the watcher children in the broker for continuity, but the operator judged the continuity loss acceptable: updates are rare, the brain re-reconciles watchers on start, and keeping watcher-child *supervision logic* out of the kernel keeps the broker narrower. The only cost is a brief window during a (rare) brain update where an offline shell could miss a wake — explicitly accepted.\n 154│\n    │...\n 156│\n*157│**Decision.** The broker tracks each session-subscriber's last-delivered cursor. On (re)start the new brain queries the broker for **all** hosted sessions and re-attaches each in **resume** mode. **Output is at-least-once** (the broker resumes from last-*sent*); **input/effects stay exactly-once** via the already-broker-owned `EffectJournal`. The explicit `BrainState` handoff *message* is **retired** for production — the new brain cold-starts and reconstructs continuity by querying the broker.\n 158│\n 159│**[V2] Generation custody.** Retiring `BrainState` would orphan the generation counter (KH 2.4: increments on every start/revive; `gen_start` always `= now()`). Custody moves to the **broker**: it owns the counter, increments it on **every** brain spawn (planned or crash), and hands `{generation, start-reason}` to the brain at spawn time via a **versioned argv/hello field** (KH-2.3 forward-compat, defaulted). Because the broker observes every respawn, this is strictly more reliable than passing it brain-...\n 160│\n    │...\n 162│- Two concrete gaps exist today: `applyhost.rs:239` re-attaches only `sessions.first()` (one session) and with `from_seq = 0` (re-replays the whole ring → duplicate output).\n*163│- Under the model-B trigger the outgoing brain is gone before the new one starts, so **no brain→brain message can be passed**; continuity must come from the persistent side. The broker already holds the per-session output ring (`broker.rs:73-143`); making it also track the delivered cursor lets a respawned brain resume gaplessly with **no disk writes** (respecting Q4) and **no handoff frame**.\n*164│- At-least-once output matches the *existing* terminal-stream contract: SPIKE-05 already established \"terminal-stream ≠ exactly-once transfer\" (a resize repaint reorders and duplicates the stream anyway). Exactly-once output would require a per-chunk ack protocol for a guarantee the terminal stream does not make; the thing that *must* be exactly-once (injected input/effects) already has the journal.\n 165│\n 166│**Alternative rejected.** *Add brain→broker acks so the broker resumes from last-processed (exactly-once output)* — cost without benefit, given the terminal-stream contract.\n 167│\n    │...\n 169│\n*170│**Decision.** If the new brain fails to reach a **ready** signal (re-attached all sessions + resumed loops) within a bounded number of boots / a healthy-run window, the broker **rolls back to the last-known-good binary** (the `spt.exe.old-N` backup), **quarantines** the bad version (no auto re-apply/re-fetch), and surfaces a loud consent-style notification. The broker holds **last-known-good + candidate** paths and chooses which to spawn (no file manipulation at failure time). Endpoints survive throughou...\n 171│\n*172│**[V1] Rollback-state-compat invariant (forward invariant, mint this milestone).** Auto-rollback spawns the **old** binary against durable state the **new** brain may already have written. This is safe *today* (a source audit confirmed zero state-migration code exists), but the first release that migrates a durable-state schema would silently break Q7's rollback. The invariant must be minted now, while it is free: **a brain must not irreversibly migrate durable state before ready-promotion** — equivale...\n 173│\n 174│**Reason.**\n*175│- The dangerous case is a *runtime* bug (new brain panics on boot), not the binary land (already rolled back at `applyhost.rs:167-170`) and not IPC mismatch (the `classify` handshake `brain_ipc_version ≥ broker.min_compatible` is checked pre-swap). A naive supervisor would crashloop the new binary: endpoints alive but logic dead — \"up but useless.\"\n*176│- The backup binary is already produced for exactly this, and the readiness+backoff machinery already exists — `peerloop.rs:82-91` has `SUPERVISE_BACKOFF_BASE/CAP` + `SUPERVISE_HEALTHY_RUN` (60 s healthy resets backoff), and `supervise_pump` is wired at `peerloop.rs:805` (M8-D4, verified). A bad update that bricks *logic* should self-heal back to last-good without a human, the same spirit as the boot-race self-heal already shipped.\n 177│- The two-phase record fixes a real existing bug: `applyhost.rs:176` writes `record_applied(version)` + `last-outcome=applied` **before** the brain handoff — i.e. before we know the new brain boots. (This is the optimistic `applied.json={version:6}` observed on `enlyzeam`.)\n 178│\n 179│**Alternative rejected.** *Bounded retry → halt and wait for manual intervention* — safer-feeling but leaves logic dead until a human shows up; inconsistent with the self-healing posture.\n    │...\n 182│\n*183│**Decision/observation.** The broker spawns the brain as a **child process** (`Command::spawn`) and they communicate over the existing local socket IPC. **No `exec` is required**, so there is no Windows/Unix divergence. ConPTY (Windows) and forkpty (Linux) handles stay broker-side and never cross to the brain. Spikes 01 and 04 already proved the handoff on both OSes.\n 184│\n 185│**Reason.** This is precisely the uniformity ADR-0004:25 sought when it rejected whole-daemon live FD-passing as \"hard and platform-divergent.\" Spawn-plus-socket sidesteps it entirely.\n 186│\n## CONTEXT-MEMORY.md\n 19│\n*20│1. **Default-to-project.** A fragment goes to project context unless it *passes the cross-project test* to be promoted to live. Promotion needs justification; demotion is free. The bias points against the leak.\n 21│2. **`current focus / next steps` is project-scoped** — the single biggest leak category. Route it to project unless the focus genuinely spans projects.\n 22│3. **Live holds a thin project roster, never project depth** — cross-project *awareness* travels; depth stays per-project.\n 23│4. **Commune-source asymmetry** (the structural fix): the live signal is strong only from **Self-authored communes**; **echo communes are project-centric** (they synthesize project-heavy session logs). So **echo communes are project-context-primary and live-context-conservative** — they make few/no live edits. This kills the leak at its source. Provenance is recorded on each block (`Source:`).\n   │...\n 91│\n*92│pi-agent-memory's **human-in-the-loop approval** (extract → review → approve, max-5-candidates) and **bash-mutation guardrails** fit its \"human curates\" philosophy. SPT's Psyche is **trusted to author its own mind autonomously** — so we adopt pi's *structure* (blocks, INDEX, bounded awareness, provenance, OCC) but **not** its approval gate. Controlled mutation is already covered by the drop-file single-writer rule (KNOWN-HAZARDS 6.4).\n## DEBUG-CONVERGE-PLAN.md\n 82│1. **Reuse the update pull/serve handshake** (`propagate.rs`). The serve leg\n*83│   already answers \"what do you have?\" during a pull. Add a *status-only*\n 84│   query (no fetch) that returns `{channel, applied_version, last_outcome}` for\n 85│   the requester's platform. Cheapest: it rides the existing update wire\n 86│   (`net/update.rs`) and the QUIC-handshake-identity origin rule (KH 7.5) gives\n 87│   the responder's identity for free.\n*88│2. **Registry/presence enrichment.** If a node already publishes its applied\n 89│   version + provenance into a registry row (the rollout metadata carries\n 90│   `git_commit`/version provenance), the coordinator can read convergence from\n*91│   rows it already syncs — no new round-trip. Verify whether\n 92│   `relcache`/registry persists \"applied version\" anywhere queryable; if not,\n 93│   prefer option 1.\n 94│\n## DEBUG-ROLLOUT.md\n 63│- `--build-current` builds and includes the coordinator's current platform.\n*64│- `--artifact <target=path>` adds an already-built artifact for another target,\n 65│  such as `x86_64-pc-windows-msvc` or `x86_64-unknown-linux-gnu`.\n 66│- `--version <u64>` overrides the local debug sequence. Use a higher value when\n 67│  recovering from lost local state.\n## DEFERRED.md\n 11│| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1. **M5 ships presence *resolution* (gossip + most-recently-active API) — its future substrate; the endpoint stays deferred** (user decision 2026-06-04). <!-- [doc->REQ-PRES-1] --> **M5-D6 seam note:** the substrate now exists — the gossiped datum (`Instance.last_active_ms`, riding the registry's epoch lease + replication) and the one...\n*12│| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal. **Note:** distinct from the v1 broker *sensing* user input on a PTY it already holds (spt-hosted) — that is in v1 and is NOT OS-wide input monitoring | Opt-in enhancement after agent-interaction presence proves out |\n 13│| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works. Consent *model* is now designed (CONTEXT §Consent & security gates); **the framework (grant store + escalation + pre-consent flags) ships M5 with this capability id reserved-but-refusing** (user decision 2026-06-04) — the capability lands into it later. Defers with it: the remote-fork arm, cross-node shell spawn, `shell_wake_spawn_anywhere` *behavior* (grant s...\n 14│| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly). **M5 ships the framework with this capability id reserved-but-refusing** (user decision 2026-06-04) | Designed alongside instantiate-anywhere's consent gate |\n 15│| Shell-binary (and harness-binary) sandboxing | Shell concept v1 | A capability toolset bounds what the *agent* can ask, not what the *binary* can do with its OS perms. Baseline stance: running any adapter/shell binary = a disclosed, accepted trust risk for all spt-core users | If/when untrusted third-party shell/harness binaries become common enough to warrant OS-level sandboxing |\n   │...\n 26│| Trust-row prune verb (`spt subnet prune <node>` or similar): registry GHOST rows now decay (4.10), but the dead identity's TRUST rows persist by design (trust is a user decision) — they cost dead dials every pump tick and clutter status views. BIGNET carries two such rows (09ef831e, both sides) | Ghost-eviction fix (2026-06-06) | Trust mutation = security surface; wants the elevation gate + the M8 noun shapes | M8 CLI nounification |\n*27│| Elevated spt-hosted endpoints — a consented elevation satellite: the broker lives inside the always-unelevated daemon (KH 5.7), so spt-hosted children are unelevated by construction; there is NO silent unelevated→elevated path on either OS (that is UAC/sudo's whole point). An agent needing admin powers is harness-hosted-elevated today (works: TCP listeners + talk-down pipes). The spt-hosted form needs a per-grant CONSENTED elevation: UAC prompt / polkit spawns a small elevated PTY-host satellite ho...\n 28│| ~~AMBIGUOUS render~~ **SHIPPED 2026-06-06** (rode the ghost-eviction fix): resolution refusals render valid copy-paste targets at the wansend boundary (`render_refusal`) — labels preferred, key-prefix on label collision, `subnet:id` for cross-subnet | M7 acceptance run (user 2026-06-06) | Pure render fix at the wansend/CLI boundary; carries into the M8 nounification sweep | DONE |\n 29│| Post-join address seeding: the pairing ceremony holds a live, authenticated connection to the peer, but the peer pumps then re-find each other from scratch via id-only discovery (fresh node ids must propagate to n0 DNS first) — observed live as ~1 min to first `status --nodes` convergence after a join. Seed the pump's address cache from the ceremony connection (both sides) for instant first sync | M7 acceptance run (2026-06-06) | Convergence is correct, just slow on first contact; needs an addr-cache...\n 30│| Clock source priority for TOTP/rendezvous: query a well-known world clock (NTP) FIRST, fall back to local time — machines with skewed local clocks currently derive wrong steps/rendezvous tokens silently | M7 acceptance grill (user 2026-06-06) | Needs an NTP client dep + offset caching + offline fallback semantics; ±1 window covers small skew today | First field report of clock-skew join failures, or M8 polish |\n*31│| CLI nounification (M8 candidate; ratified shape from the 2026-06-06 grill): `spt endpoint` namespace absorbs fork/suspend/wake/shutdown/rename/stop/digest + `access` (per-endpoint store — NOT subnet-scoped) + `description` (ex-`resources` blurb; bare = show, `set` = author); `spt endpoint list [--local\\|--subnet <name>]` = merged listing, default all-subnets grouped by subnet, SELF endpoint pinned distinctly at top (bare `spt endpoint` = this list); agent HOT PATH stays flat (`send`/`ring`/`ready`/`w...\n 32│| ~~Node label for an endpoint-less node (REQ-SUBNET-3 gap)~~ **SHIPPED 2026-06-08** (option 2 — node-level presence datum): a peer with **zero endpoints** rendered as a bare key-prefix in `status --nodes` because `node_label` only rode endpoint `Instance` rows. FIX: a node-LEVEL carrier — `SubnetRegistry.node_labels` (node→label) + a `NodeLabelUpdate` feed record riding the existing registry replication stream as an untagged `RegistryFeedRecord` variant (instance bytes unchanged → mixed-version ...\n*33│| **Instant + never-seen peer hostname via pairing-time capture (REQ-SUBNET-3 follow-up; the deferred \"option 1\")**: the shipped node-level label (option 2) is GOSSIP-ONLY, so a peer's name is learned only from a pump round while it is alive. Two residual gaps remain after the eviction fix below: (a) ~1 gossip cadence to appear after a join (the joiner trust-pins by pubkey with `label=None`; the hostname arrives next pump round — enlyzeam↔kitsubito converged to `ENLYZEAM` after a cadence, fine but no...\n 34│| ~~Subnet-scoped liveness probe (REQ-SUBNET-5 detach gap)~~ **SHIPPED 2026-06-08** (REQ-SUBNET-5 int stage): `subnet status --nodes` showed a DETACHED peer as **online** because liveness fell to `Probe` → a raw transport dial by node-pubkey, which succeeded on the peer's still-bound daemon endpoint (one ALPN `spt-core/net/0` serves all subnets, so the dial was subnet-blind). FIX: a new `ServeProbeRecord` (spt-net `serveprobe`) + daemon `serveprobe` handler/requester ask the peer \"serving subnet X?\", a...\n 35│| **Subnet full-mesh visibility (PLANNED MILESTONE — `SUBNET-MESH-PLAN.md`)**: non-directly-paired members never see each other — a subnet is currently the PAIRING GRAPH, not a mesh (A↔B, C↔B; B offline → A and C invisible; even with B online, B doesn't relay). Root: pairwise TOFU trust (`trust.rs`) + own-rows-only gossip / no transitive relay (`peerloop.rs`, KH 4.10/7.5) + no member roster in the pairing seed transfer. FIX = member-AUTHENTICATED subnet data (seed-MAC and/or per-node signatures...\n 36│| `xtask debug-converge` — automated convergence watcher for debug rollout (the REQ-UPD-6 first-slice item ADR-0016 names): watch every expected debug-pinned reachable lab node until all report the target version applied, else a per-node timeout table (Offline / StagedAwaitingConsent / BlockedByBrokerResources / Rejected{reason}). Activates REQ-UPD-6's `int` stage. Full build plan: `docs/DEBUG-CONVERGE-PLAN.md` | REQ-UPD-6 first slice (2026-06-06) | The first slice ships manual \"Apply and observe\" (`DE...\n*37│| **Origin-source update bootstrap (`spt update fetch` — pull the latest signed release from GitHub into the existing verify→stage→apply pipeline)**: today update DISCOVERY is **peer-only** (REQ-UPD-1: a daemon pulls offers from roster peers via `request_update`; `propagate.rs`/`peerloop.rs`). There is NO origin fetch, so the **first node in a fleet — or any isolated/solo node — cannot get a new release without a peer that already staged it** (the fleet converges by gossip, but only *after* a m...\n 38│| **Test broker socket-bind hardening (CI-flake removal)**: `applyhost` tests' `served_broker` helper does `Broker::bind(name).expect(\"bind broker\")` (`crates/spt-daemon/src/applyhost.rs:293`) where `name` = `spt-daemon-d7b-{pid}-{seq}.sock`. Flakes intermittently on the **kitsubito** self-hosted Linux runner (likely AF_UNIX path collision / stale socket file under the runner's reused workspace — Windows named pipes don't hit it). Cost the v0.3.2 release a full red CI run + a `--failed` rerun (2026-06-...\n 39│| **`spt update fetch` single→set shape-upgrade at same version (REQ-UPD-8 transitional trap)**: `cmd_update_fetch` sets the rollback floor = `staged_version` (the monotonic counter), and the gate is a plain `candidate > floor`. A node that fetched+staged version N as a platform **single** (pre-0.3.2 fetch behavior) can never re-fetch version N as a multi-platform **set** — same counter → `Rollback{current:N, candidate:N}`. Bites the **seed-node bootstrap**: the first node to origin-`fetch` a relea...\n 40│| **On-disk `current_exe` reconcile after auto-rollback (the D6-2 reboot residual)**: readiness-gated auto-rollback (ADR-0018 Q7, restoration D6-2) is **record-driven selection** — after a rollback the broker's supervisor keeps spawning the last-known-good `.old-N` binary, chosen from the durable `RolledBack` record, so the rescue survives a reboot for free **with no file rename at the failure instant** (the brittle path the design rejected). But the bytes on disk at `current_exe` are STILL the quarant...\n 41│| **Quarantine operator override (`spt update apply --force` — the note-i escape hatch)**: D6-2's quarantine guard refuses re-applying a version that auto-rolled-back on this node (the `RolledBack` record is the marker; clears naturally when N+1 stages). But a version whose failure was **environmental** (a transient boot condition, not bad bytes) leaves the node **permanently allergic** to a genuinely-good version with no escape but staging a newer one. FIX = an explicit operator override (`spt update ...\n*42│| **✅ RESOLVED — shipped v0.8.3 (REQ-HAZARD-BROKER-QUIC-DEADLINE, KNOWN-HAZARDS 7.8).** ~~Broker-side bound on brain-waiting QUIC ops (the pump-IPC-deadline B-half)~~: the brain-side deadline (REQ-HAZARD-PUMP-IPC-DEADLINE, KNOWN-HAZARDS 7.6) stops the single-threaded pump wedging when a peer black-holes, by bounding the pump's brain-IPC reads and escalating a TimedOut to a supervised restart. But the ROOT cause is broker-side: the broker's `net_open_stream` / `net_stream_send` / `net_dial` handlers m...\n 43│| **Durable in-daemon alarm scheduler (one-shot deadline machinery — the Q4/V3 deferral)**: the broker/brain restoration (ADR-0018 Q4) fixes the *rule* for one-shot scheduled events — persist the absolute `target-time` at creation; every brain start (update *or* crash) reads it and fires-if-due; **never reset on crash** (a user's \"remind me at 3pm\" is a commitment that must outlive any restart). But it deliberately does **not** build the machinery: the spt-core daemon has **no one-shot consumer today...\n 44│| **Autonomous session-digest freshness + Option-C re-home (M9 ADR-0008 amendment deferral)**: M9 re-founded the digest as an ON-DEMAND projection of normalized session logs (`spt-term::projection`; `spt-daemon::digest::project_endpoint_digest`) — snapshot-pull and the structured-delta-stream CONTRACT ship, deltas driven by pulls / `api digest-entry` pushes. Two pieces deferred, both because they have **no consumer yet** (Shell=M10, GUI/frontend pane, Gateway agent-window — none exist): (a) the **aut...\n 45│| **Two-origin `owl_message` context-tap subtype refinement (M10 ADR-0019 deferral)**: the two-origin merge's owl-message producer (`spt::api::startup` `deliver` closure) taps the live-agent **`emit` chokepoint**, so EVERY delivered frame — including resurfaced **notif** frames and shell-context rows — is recorded as a single coarse `owl_message` context-injection entry (`context_kind:\"owl_message\"`). The data is not wrong (a notif IS injected context), only coarsely labeled; the digest is a glanceab...\n*46│| **W2.5 — attached-presence + Kick-and-attach (M12 picker blue tri-state)**: M12 W2's picker ships online/offline status only; the **blue ■ \"attached\" tri-state** + the **\"Kick `<node>` and attach\"** confirm option were SCHEDULED (operator 2026-06-14, NOT dropped) as a dedicated slice built AFTER W2 lands, then wired back into the picker. Scope: (1) a **broker attach-presence query** — \"who is attached to endpoint E's PTY, by which node/surface\" — derive from the `presence.rs` PresenceLog conn c...\n 47│| **Cross-node shell TUNNEL relay (REQ-SHELL-4 cross-node-on-LAN carry)**: W3 built + proved the opaque byte tunnel **same-node only** (broker-homed `TunnelHub` loopback stream pair + the local control socket; `tunnel_e2e` round-trips `<EVENT`-looking bytes byte-exact). The **cross-node** path does NOT exist: `StreamFamily` (dispatch.rs) has no `Tunnel` variant, `classify_first_line` no tunnel arm, `SHELL_LINK` carries only relink/cmd/drive (no tunnel action), and every tunnel surface (`tunnel_ensure/sen...\n*48│| **Per-connection contiguous-sent cursor — structural guard against a decreasing-floor controller re-take (`REQ-HAZARD-CONTROLLER-WRITER-REORDER` follow-up)**: v0.13.0 P1c fixes the controller-writer reorder while KEEPING handoff's eager subscribe (fix #1 \"drop the subscribe\" was reverted — it's the standalone-resume mechanism), so the decreasing-floor double-take (handoff subscribe@K + serve_attach subscribe@0) is still PRESENT and made safe by: `controller_writer` epoch-gated under `send.lock()` (...\n 49│| **Adapter-manifest-enabled rc VT/mouse mode (`spt rc` scroll vs right-click-paste xor resolution)**: v0.13.0 P1/P1b's Windows `EnableMouseCapture` (for client-originated right-click bracketed paste, 7.18) makes Windows Terminal hand ALL wheel events to rc instead of scrolling its own scrollback; rc forwards the wheel to the harness as SGR reports (7.20) but ONLY when the harness has mouse-reporting on (`enabled && sgr`) — and **Claude Code's REPL never enables mouse tracking** (it has no internal scr...\n*50│| **Always-on endpoint kind + `#`-sigil addressing (ADR-0023; REQ-EP-8 / REQ-EP-9) — the core seam for adapter-bridged group/forum messaging**: a `[always-on]` manifest section declaring a resident, owner-less, addressable endpoint binary the daemon boot-launches + supervises **continuously** (independent of agent liveness, register-triggered, one per `<adapter>[:profile]`), which self-manages its `#`-addressed channel endpoints via the existing `api bind` and may call the already-ungated `endpoint wak...\n## DOCS-STRATEGY.md\n  10│\n *11│1. **Sub-10-minute killer quickstart** — runnable, deterministic, whole-working-thing-first, no placeholders. TTFHW (\"time to first hello world\") is the single most-cited conversion lever (~68% abandon trials over setup time).\n  12│2. **Diátaxis four-mode separation** — tutorial / how-to / reference / explanation, never mixed (mixing is the most-cited cause of confusing docs).\n  13│3. **Deterministic, real, copy-pasteable examples everywhere** — no `<YOUR_VALUE_HERE>` placeholders; real values that run. Serves humans *and* agents.\n  14│4. **Dual-depth agent exports** — `llms.txt` (slim curated index) + `llms-full.txt` (full concatenated export), auto-generated in CI, plus markdown content negotiation (`.md` suffix / `Accept: text/markdown`) which cuts agent token use ~90% vs HTML.\n    │...\n  86│## Capabilities      (one bullet per vertical, each → its own llms.txt)\n *87│- [Messaging](…/messaging/llms.txt): typed ops + file blobs; send / ring / ready.\n  88│- [Live-agent lifecycle](…/lifecycle/llms.txt): skeleton → live → dormant → suspended.\n  89│- … Terminal · Networking · Harness contract · Instances · Shells · Self-update\n  90│\n    │...\n 105│   Verify:                      spt --version      # spt 0.1.0 (proto v1)\n*106│## 2. The whole working thing   spt ready alice\n*107│                                spt ready bob\n 108│                                spt send bob --from alice \"hello\"\n 109│## 3. Run it                    spt poll --as bob\n 110│                                # → from=alice op=message body=\"hello\"\n    │...\n 146│\n*147│Top-level `--help` groups by audience (`AGENT COMMANDS` send/ring/ready/poll · `HARNESS COMMANDS` api … · `NODE COMMANDS` pair/subnet/update). Per-command: **USAGE · FLAGS · EXAMPLES (real values) · LEARN MORE**. Beyond gh: a **`spt <cmd> --help --json`** structured-help mode so dev-agents parse the contract without scraping, plus gh's uniform **`--json <fields>` / `--jq` / `--template`** trio on any list command. The `--json` help export is generated + CI-gated alongside rustdoc and the schema.\n## F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md\n   4│perri field seeds + spawn-fail taxonomy (2026-07-03, 16-part dispatch,\n  *5│observed-vs-inferred flagged), doyle code recon of the spawn/ready seam\n   6│(anchors below, verified @b6d8357), perri design-review answers Q1–Q4\n   7│(2026-07-03; all four folded in — see §7).\n   8│\n    │...\n  23│its SessionStart hook, so `establish_perch` never runs (api/startup.rs:299 is\n *24│the ONLY writer of the `ready` marker), and the endpoint sits `ready=false`\n  25│**forever, silently** — while the operator was told it started.\n  26│\n *27│`ready=false` is already rendered by `spt list`/picker (roster.rs:69 →\n  28│cli.rs:2735/3826) but nothing gates or alarms on it: passive display only. The\n  29│attach path's `await_endpoint_session` (cli.rs:1854, 25 s) gates on broker\n  30│**session existence** — created synchronously at spawn, so it passes instantly\n    │...\n  41│|---|------|----------|-------|\n *42│| 1 | TRUST-WEDGE — CC at workspace-trust dialog, null stdin; `ENDPOINT_RUN_STARTED` printed; ready=false forever | every fresh probe cwd, this session | SILENT |\n  43│| 2 | ACCOUNT-ROOT-MISMATCH — trust seeded in a DIFFERENT config root than the spawn resolves; wedges despite a valid-looking seed | Reavo/Brandon dual-root box (si1 vs b6b probes) | SILENT |\n  44│| 3 | PSYCHE-CRASHLOOP — nested psyche CC dies at trust dialog; wrapper respawned ~3/s, ordinal 5358 in 30 min | F-h; now guarded by adapter v0.13.0 give-up + core C3(b) | SILENT (guarded) |\n  45│| 4 | ADAPTER-UNRESOLVED — no registered adapter hosts the parent binary | seed→listen path, bash parent | LOUD pre-spawn (good shape) |\n    │...\n  67│The silent class (modes 1, 2, 3, 6) must fail with the same loudness as the\n *68│pre-spawn class (modes 4, 5) already does.\n  69│\n  70│### Half A — bind-deadline honesty (spt-core, harness-agnostic)\n  71│\n  72│After the spawn RPC returns in `cmd_endpoint_run --start`, the CLI **awaits\n *73│bind** — the appearance of the `ready` marker (`resolve_ready_file`, the\n  74│`establish_perch` effect) — bounded by a deadline:\n  75│\n  76│- **Success:** `ENDPOINT_RUN_STARTED:{id}` moves to AFTER the bind is observed\n    │...\n  82│  picker render the honest state. A later successful bind clears the stamp\n *83│  (`establish_perch` already rewrites state; clearing rides there).\n  84│- Classification probes (all CLI-observable; no daemon change needed to\n *85│  detect): child pid alive? broker `has_session` still true? ready marker\n  86│  present? →\n  87│  - `WEDGED_PRE_BIND` — pid alive, session alive, never bound (modes 1, 2)\n  88│  - `EXITED_PRE_BIND` — child exited before binding (mode 5-shaped recipes\n    │...\n  90│  - `NO_SESSION` — broker session vanished mid-wait (mode 6 shape)\n *91│- Deadline: **default 60 s** (field-confirmed: real binds land t+5–15 s;\n  92│  every observed bringup exceeding ~20 s was a wedge that never bound — the\n *93│  deadline separates cleanly at ≥30 s; **never configure below 30 s**),\n  94│  overridable per-adapter via a manifest key (the bringup-time contract\n  95│  belongs to the adapter) and per-invocation via `--bind-deadline <secs>`\n  96│  (the per-node escape for cold-broker first-spawn / loaded boxes). Escape\n    │...\n 131│   via `resolve_program_in_dir` like every other adapter-manifest program.\n*132│3. **Bounded + loud:** runtime capped (30 s); non-zero exit or timeout ABORTS\n 133│   the session spawn with `ENDPOINT_SPAWN_FAIL:{id}: reason=PRE_SPAWN_FAIL:\n 134│   <exit/detail>` on CLI stderr (relayed through the spawn RPC error — this\n*135│   path already reaches the CLI as `ENDPOINT_RUN_FAIL` does today).\n 136│4. **Idempotency contract:** the command must be safe to run on every spawn\n 137│   (documented in MANIFEST.md; the seeder re-asserting an existing trust entry\n 138│   is a no-op).\n    │...\n 163│  owlery cwd is trust-seeded by the same pre-spawn seam, and the crashloop\n*164│  backstop (C3(b) + adapter give-up) already guards the symptom.\n 165│- No mode-6 work: FIXED on 0.23.0 by F-028 B1/B4 (perri definitive repro).\n 166│  The Half-A deadline token still catches its shape if it ever regresses.\n 167│\n    │...\n 174│| 3 PSYCHE-CRASHLOOP | Half B (owlery cwd seed) | existing C3(b)/v0.13.0 guard |\n*175│| 4 ADAPTER-UNRESOLVED | — (already good) | existing pre-spawn error |\n*176│| 5 STALE-CLI-CONTRACT | — (already good) | existing / Half A `EXITED_PRE_BIND` |\n 177│| 6 RELAUNCH-NEVER-BINDS | FIXED (F-028 B1/B4, 0.23.0-verified) | Half A `NO_SESSION`/`WEDGED_PRE_BIND` (regression net only) |\n 178│\n 179│## 5. Requirement seeds (register at wave start, `required_stages=[]` until active)\n    │...\n 206│\n*207│1. **Deadline default — ANSWERED:** 60 s safe with margin (real binds t+5–15 s;\n*208│   every >20 s bringup was a wedge, never a slow success). Floor 30 s; per-node\n 209│   override kept for cold-broker first-spawn / loaded boxes. Folded into §3.\n 210│2. **`pre_spawn` on resume — ANSWERED: YES.** Resume re-checks trust (INT-2:\n 211│   resumed CC died at the trust dialog). Folded into §3 Half B.\n## F-030-PSYCHE-EPHEMERAL-DESIGN.md\n   2│\n  *3│Status: **READY FOR BUILD 2026-07-04 — wave called by operator.** Author: doyle.\n   4│Inputs: perri `PSYCHE-EPHEMERAL-PLAN.md` (adapter half, doyle-gated PASS; built on\n   5│parked branch `psyche-ephemeral-parked`, publish held for this core wave), the\n   6│2026-07-04 field case (both directions — v0.13.2 bad-ship + hall-bf multi-subnet\n    │...\n  25│  ~5 s reconcile tick when it isn't resident.\n *26│- Meanwhile the **per-event driver stack already exists, tested, with ZERO\n  27│  production callers**: `resume_psyche` (spt-live resume.rs:175, mode-validated\n  28│  spawn + nested rebind), `run_psyche_turn` (spt-live turn.rs:72, bounded\n  29│  stdin→stdout turn; its only production caller is the ADR-0013 conflict-merge\n    │...\n  36│**(a) The adapter cannot HOLD residency.** The resident runner's per-pulse\n *37│liveness poll (`spt ready <id> --once`) hard-refuses on a multi-subnet home\n *38│(`READY_FAIL: … pass --subnet`). The nested psyche process **cannot know its\n  39│subnet/home**: core fills only `base_keys` = `{id}`, `{session_id}`, `{node}`\n  40│(lifecycle.rs:338) plus role-local extras — no `{subnet}`, no `{home}`. On\n  41│hfenduleam (2 subnets since BIGNET joined 7/3) the runner died instantly,\n    │...\n  48│exit after the seed turn tripped the residency machinery, which tore down the\n *49│endpoint's hosted state — ready marker removed, never re-stamped, every\n  50│force-native gated `leg=cli-gate-not-hosted` **permanently** (field brick,\n  51│operator-hit on hall-bf, contained same-day by v0.13.3).\n  52│\n    │...\n  61│  image only for a bounded turn — the own-copy machinery loses its reason.\n *62│- Psyche TURNS already work daemon-driven today (`psyche_download`, revival\n  63│  prompt, reconcile merge) — proof the per-event shape is the natural one.\n  64│\n  65│## 2. Field evidence (2026-07-04 — provenance)\n    │...\n  70│| 2 | hall-bf-psyche boot-churn ~5 s cadence, ordinal 6491+, synthetic `sess-<pid>-<ts>` ids, no real session ever binds; starts exactly at BIGNET join (7/3) | operator box, real home | adapter can't hold residency |\n *71│| 3 | Poll-verb root repro: `spt ready <id> --once` on 2-subnet home → exit 1, `READY_FAIL … pass --subnet`, empty stdout | perri, clean repro | adapter can't hold residency |\n *72│| 4 | v0.13.2 shim-exit → residency teardown → ready removed → permanent `leg=cli-gate-not-hosted` | hall-bf + perri wake-confirm | residency expectation destructive |\n *73│| 5 | Psyche turns land fine via daemon-driven paths while the resident churns | hall-bf digest (`psyche_download` at every boundary) | per-event shape already proven |\n  74│| 6 | ~12/min re-host cadence NEVER tripped the C3(b) thrash guard (boot records are apparently not ledger boundaries to the guard) | hall-bf | guard blindness (rides this wave) |\n  75│\n  76│## 3. Design\n    │...\n 101│budget/cooldown shape): N consecutive turn failures → stamp\n*102│`psyche_host_error` (kept name; it is already the harness-reachable pattern) +\n 103│cooldown, reset on a successful turn. No respawn storm exists anymore because\n 104│there is nothing resident to respawn.\n 105│\n    │...\n 115│- `resume_psyche(ResumeMode::…)` validates the custody key before spawn\n*116│  (already implemented — resume.rs:183).\n 117│- **Reseed path:** when the psyche's own session is lost/invalid (first host,\n 118│  store wipe, harness-side expiry), fall back to\n 119│  a fresh-with-preload turn — `download_psyche_context` composes\n    │...\n 136│- **The teardown that made direction (b) destructive is deleted**: no psyche\n*137│  state may ever remove the PARENT endpoint's ready marker or hosted state.\n 138│  Psyche trouble stamps psyche fields only. (This is the conformance test's\n 139│  core assertion.)\n 140│- C3(b) guard rework rides here (evidence #6): the failure budget counts\n    │...\n 149│**nested `{parent}/{nested}` ids resolve under the parent's home** —\n*150│subnet-less resolution for nested perches (`resolve_perch_path`/ready-verb\n 151│family). Additionally expose a single `{subnet}` base_keys fill **when\n 152│known** (lifecycle.rs:338; absent → LOUD missing-key fail, the `{node}`\n 153│precedent on :344). **There is deliberately NO `{home}` key** (doyle W4 Q1: the\n*154│endpoint home subnet is one concept, and `home:` is already the subnet-name\n 155│qualifier position — a `{home}` template key would only invite meaning-drift).\n 156│Red-first = evidence #3's exact repro: 2-subnet home, nested-id verb, must\n 157│succeed.\n    │...\n 195│  rig, not separately fixed — the ephemeral model should erase it; if a\n*196│  residue survives, it becomes its own seed with the rig already in hand.\n 197│\n 198│## 4. Coverage matrix\n 199│\n    │...\n 225│  0.13.2): psyche failure of ANY shape must not remove/alter the parent\n*226│  endpoint's ready/hosted state, and hosting must not churn-respawn.\n 227│  Conformance int = the hall-bf shape: multi-subnet home, live endpoint,\n 228│  failing psyche → parent stays deliverable, no rehost churn, error stamped.\n 229│- `REQ-HAZARD-THRASH-GUARD-BLIND` — hazard (paid-for, evidence #6): the\n    │...\n 254│   timeouts; the pulse-fired turn needs a config default — propose the\n*255│   existing pulse-period-derived bound, floor 30 s).\n 256│2. Does the checkpoint/commune choreography want a custody-transition turn at\n 257│   parent boundary (psyche notices the parent reset), or stay purely\n 258│   event-driven? (Design assumes purely event-driven; cheap to add later.)\n 259│3. `{subnet}` fill naming — RESOLVED (doyle W4 Q1): a single `{subnet}` key,\n*260│   no `{home}` key (the endpoint home subnet is one concept; `home:` is already\n 261│   the subnet-name qualifier position). Confirmed against CONTEXT.md glossary.\n## F-035-CONFLICT.md\n  3│**Raised by:** todlando, mid-build, 2026-07-09. **Build HELD** (WIP uncommitted on\n *4│`f-035-window-drain-honor`). doyle's COLLAPSE ruling collides with a **shipped**\n  5│acceptance gate my RCA (docs/F-035-RCA.md) missed. Need an A/B/C ruling before I proceed.\n  6│\n  7│---\n   │...\n 13│\n*14│**`crates/spt/tests/idle_edge_drain_e2e.rs:89`** — `fn spool_while_active_then_idle_fires_injection`\n*15│`// [int->REQ-MSG-IDLE-EDGE-DRAIN] [int->REQ-HAZARD-DELIVERY-STARVATION]`\n 16│\n 17│- **Setup:** spools `spool_message_deferred_at(perch, \"peer\", …)` — which is\n 18│  `insert_message(…, WINDOW_ACTIVE_ONLY, …)` (spool.rs:363). So the row is\n   │...\n 26│\n*27│`REQ-MSG-IDLE-EDGE-DRAIN` (F-023 leg 2) and `REQ-SEND-WINDOW-DRAIN-HONOR` (F-035) are\n 28│directly incompatible for the relay-less spt-hosted class. The active/idle asymmetry I\n 29│called a bug in the RCA is **intentional** for that class.\n 30│\n   │...\n 35│For an **spt-hosted** endpoint (broker owns the PTY, `controllable`, relay-less), the\n*36│idle-edge/parked drain is the **ONLY** carrier for an `active_only` row. Traced every path:\n 37│\n 38│| carrier | `active_only` to spt-hosted? |\n 39│|---|---|\n   │...\n 41│| Send-time idle re-offer (`cli.rs:5734`) | **NO** — gated `&& !active_only`. |\n*42│| Hook poll `drain_active_window` (`api poll`) | **N/A** — that is the HARNESS-hosted hook channel; an spt-hosted endpoint has no `api poll` harness. |\n*43│| **Idle-edge / parked drain `drain_idle_spool`** | **YES** — the F-023 leg-2 behavior (`include_deferred = !resting`). **This is the only carrier.** |\n 44│\n 45│So: `active_only` is designed as *\"the active **hook** window\"* — meant for\n 46│**harness-hosted** endpoints that `api poll`. An **spt-hosted** endpoint has no hook, so\n   │...\n 59│  `active_only`+spt-hosted is knowingly revoked (that class starves, or gets an\n*60│  evaporate/refuse-at-send policy instead). Requires re-scoping `REQ-MSG-IDLE-EDGE-DRAIN`\n 61│  + rewriting its int.\n 62│- **(B) never-starve wins** — the idle edge *should* carry `active_only` to spt-hosted\n 63│  (F-023 correct). Then **F-035 is mis-scoped**: *\"never wakes\"* is the **harness** hook\n   │...\n 65│  never a contract for the hook-less spt-hosted class. **RETIRE or NARROW\n*66│  REQ-SEND-WINDOW-DRAIN-HONOR; no code change.**\n 67│- **(C) class split** — spt-hosted delivers `active_only` at idle **without waking**\n 68│  (non-interrupting background inject) vs harness-hosted holds-for-poll. New behavior,\n 69│  larger; needs a \"deliver-but-don't-wake\" inject mode the translation binary honors.\n   │...\n 71│**todlando lean:** the carrier table points at **(B)** — the field-doc *\"never wakes an\n*72│idle target\"* (cli.rs:85) describes the harness hook model, and F-023 already reasoned\n 73│through the spt-hosted no-hook gap deliberately. The original F-035 *symptom* (a\n 74│`--deferred` background row reaching an idle spt-hosted agent) may be **working as\n 75│designed** — it is the only way that class can ever be delivered. If the true concern is\n*76│*\"it interrupts,\"* that is **(C)**, not the drain-exclusion of **(A)**. But this is your call.\n 77│\n 78│My WIP (spool.rs window-axis collapse + inject.rs + 2 units) is staged **uncommitted** — I\n 79│touched neither delivery.rs nor worker.rs. Standing by for A / B / C.\n## F-035-RCA.md\n  *1│<!-- [doc->REQ-SEND-WINDOW-DRAIN-HONOR] F-035 RCA — report-before-fix. -->\n  *2│# F-035 RCA — idle-edge parked-drain ignores the delivery-window tag\n   3│\n   4│**Status:** RCA-first, **report before fixing** (doyle-dispatched 2026-07-09).\n  *5│**REQ:** `REQ-SEND-WINDOW-DRAIN-HONOR` (registered, `required_stages = []` — activate on doyle's fix ruling).\n   6│**Investigator:** todlando. **Off:** `origin/main@2c05dc9` (v0.30.2).\n   7│\n   8│---\n    │...\n  20│\n *21│For an **spt-hosted, relay-less** endpoint (the class the idle-edge drain targets),\n  22│such a row is delivered through the translation binary at the idle window — the\n  23│exact delivery the tag forbids.\n  24│\n    │...\n  42│\n *43│### ACTIVE drain — honors the axis (correct)\n  44│\n *45│`spool::drain_active_window_inner`:\n  46│\n  47│```\n  48│include_deferred ? \"WHERE delivered=0 AND \\\"window\\\" != 'idle_only'\"\n    │...\n  53│\n *54│### IDLE-edge drain — ignores the axis (the bug)\n  55│\n *56│`spool::claim_idle_edge_inner` (reached via `spt_daemon::inject::drain_idle_spool`\n *57│— the **one shared leg** the active→idle edge drain `delivery.rs:drain_idle_window`,\n *58│the send-time already-idle path, and the pulse re-offer belt all call):\n  59│\n  60│```\n  61│include_deferred ? \"WHERE delivered=0\"\n    │...\n  66│\n *67│`drain_idle_spool` passes:\n  68│\n  69│```\n  70│let include_deferred = !crate::resting::deferred_held(&perch_path);\n    │...\n  78│\n *79│The drain **conflates two orthogonal axes**, using the `deferred` flag for both:\n  80│\n  81│1. **Resting gate** (REQ-INST-6 / KH 1.4): *hold deferred rows while the owner is\n  82│   dormant.*\n    │...\n  96│  `claim_idle_edge_inner` reads. Live, not theoretical.\n *97│- **\"The resting gate already handles it.\"** Refuted — it only excludes them while\n  98│  *resting*; the active→idle edge on a non-resting endpoint is the leak path.\n  99│- **\"`idle_only` is symmetric-broken too.\"** No — `idle_only` has `deferred = 0`, so\n 100│  both idle-claim branches include it; it correctly rides the idle window. Only the\n 101│  `active_only`→idle direction is wrong.\n*102│- **\"It's `drain_idle_window`'s caller.\"** No — the caller correctly fires on the\n 103│  edge; the defect is the SQL selection in `claim_idle_edge_inner` (the shared spool\n 104│  leg), so every one of the three callers inherits it.\n 105│\n    │...\n 109│\n*110│Mirror the active drain: make the idle claim honor the **`window` axis**, excluding\n 111│`active_only` unconditionally, and keep the resting hold as an **orthogonal** filter.\n 112│\n 113│```\n*114│-- idle-window eligibility (mirror image of drain_active_window_inner):\n 115│WHERE delivered=0 AND \"window\" != 'active_only'\n 116│```\n 117│\n    │...\n 119│dormant) rather than *defining* it. Net effect: `default` + `idle_only` ride the\n*120│idle window; `active_only` never does; the two drains become mirror images on the\n 121│`window` axis.\n 122│\n 123│**Open ruling for doyle:** whether the resting `include_deferred` parameter on the\n    │...\n 139│\n*140│Kin: `REQ-MSG-DELIVERY-AXES`, `REQ-MSG-IDLE-EDGE-DRAIN`, `REQ-IDLE-PARKED-DELIVERY`,\n 141│`REQ-INST-6` (the resting gate it conflates).\n## F-035-RELAY-RCA.md\n  9│\n*10│## Finding: every `active_only`→relay path on current main is ALREADY guarded\n 11│\n 12│I traced every way an `active_only` row could reach lia's relay (its live PollListener\n*13│via `deliver_tcp`, OR its backlog via `drain_backlog`). **All are closed on current main:**\n 14│\n 15│| path | carries active_only to the relay? | why not |\n 16│|---|---|---|\n   │...\n 18│| Cross-node `spt send` (WAN) | **NO** | `cli.rs:5762` — a remote active_only that misses locally returns *\"active-only stays local-only\"* and does NOT `wan_send`. The `WanMessage` wire record has **no window field** (wanmsg.rs:38), and the sender deliberately keeps active_only local-only rather than drop the class over the wire. So active_only never reaches `receive_wan`/`deliver_tcp` (wan.rs:300). |\n*19│| Relay backlog (`relay.rs:63 drain_backlog`) | **NO** | `drain_non_deferred` = `WHERE delivered=0 AND deferred=0`; `deferred=1 IFF active_only` (spool.rs:46). Excluded. (doyle already confirmed.) |\n*20│| Relay live-poll-forward (`relay.rs:77 pump_once`) | **NO** | Its only feed is `deliver_tcp`→the PollListener; no unguarded `deliver_tcp` of an active_only user msg exists (`deliver::deliver` callers = ring/ready ping-pong + tests; `reporting.rs:456` is a notif frame; `reporting.rs:161` shell-context uses `send_deferred`→**spool**, not tcp). |\n*21│| Parked-idle re-offer belt (`livehost.rs:265 drain_idle_spool`) | **NO** | BELT-1 `relay_present → Skip` (livehost.rs:495); and `claim_idle_edge` (post-nothing) is guarded `is_spt_hosted_no_relay`. lia has a relay → never runs. |\n*22│| busy→idle relay start (`relay.rs:90 run`→`drain_backlog`) | **NO** | same `drain_non_deferred` exclusion — on going idle, only `default`+`idle_only` spooled-while-busy rows drain to the relay; active_only stays spooled for the next busy poll. |\n 23│\n 24│The **only** carrier for a spooled active_only row is lia's hook **poll**\n*25│(`api poll`→`poll_drain`→`drain_active_window`, `WHERE \"window\" != 'idle_only'`, which\n 26│includes active_only) → delivered to the **polling session's stdout**, never the relay.\n 27│\n 28│**Conclusion:** on current main, spt-core's send/deliver/relay/spool/poll legs do **not**\n*29│leak active_only to a relay. Corrected-scope `REQ-SEND-WINDOW-DRAIN-HONOR` (poll-only for\n*30│a relay-bearing live agent) appears **already satisfied** by these legs.\n 31│\n 32│---\n 33│\n   │...\n 38│\n*39│1. **Already fixed.** lia's box may run an spt-core **older** than the `send_windowed:217`\n 40│   / `cli.rs:5762` guards. → Confirm lia's `spt` version; if pre-guard, this is a\n 41│   version-lag, not a current-main bug (verify-on-upgrade, no code change).\n 42│2. **Adapter-side handoff.** The busy↔idle **poll→relay handoff** is the adapter's\n 43│   (spt-claude-code) job, not spt-core's `api state idle`. If the adapter does a final\n*44│   `api poll` on going idle (draining active_only) and injects the result into the\n 45│   now-idle session, that's an adapter route — outside these legs. → perri's lane.\n 46│3. **A repro detail I'm missing.** → Need the exact send form (`spt send --active-only`?\n 47│   a shell-context/`send_deferred`? a notify?), the sender's node vs lia's node\n## FAULT-MATRIX.md\n 28│| 9 | **Broker process restart (journal survives, conns don't)** | Live conns/streams lost | Effect journal (durable) | Deduped op whose resource is gone → typed \"retry with a fresh op_id\" error; brain re-dials; an `spt rc` operator viewport RECONNECTS on its own — centered banner, full establish re-drive inside a bounded window, then a plain-language give-up (REQ-RC-RECONNECT) <!-- [doc->REQ-RC-RECONNECT] --> | `broker.rs::dispatch_net_dial` / `dispatch_net_stream_open` restart arms; `rc.rs` reconne...\n*29│| 10 | **WAN msg feed replayed** (sender redrive, or receiver brain restart resubscribing from a stale cursor) | None — replayed records dedup | Spool state (each op exactly one row); already-delivered ops | Durable `wan_seen` op-id claims absorb the replay; spool path claims atomically with the row | `tests/wanmsg.rs::receiver_restart_replays_feed_without_double_delivery`; `spool.rs` wan unit tests |\n 30│| 11 | **WAN msg record forges its origin** (payload `origin_node`/`node` field) | None — forgery inert (unknown field) | Access-gate subject (handshake-proven `remote_id_hex`) | None needed (records carry no origin field by design — KH 7.5) | `tests/wanmsg.rs::wan_message_lands_exactly_once_under_transport_origin`; `wanmsg.rs::forged_origin_field_is_inert` |\n 31│| 12 | **Target brain killed mid-remote-attach** (operator typing into the dead window) | Pumping pauses until handoff | Broker-held QUIC stream, session + child, output log, effect journal; the operator's viewport | Successor re-serves the same stream from seq 0 (worst case): replayed input dedups at the PTY-write journal; re-transmitted output dedups at the operator's render cursor — every byte exactly once | `tests/attach.rs::attach_survives_target_brain_restart_exactly_once` |\n*32│| 13 | **Target brain killed mid-file-transfer** (push receive: partial temp file on disk) | Transfer pauses until handoff | Broker-held QUIC stream + ring, the partial temp, the durable progress record (last observed position) | Successor re-serves the same stream from seq 0 (worst case full replay): chunks carry absolute offsets — replayed bytes rewrite in place; the commit is an atomic temp→final rename gated on temp completeness, and a replayed commit dedups against an already-committed final (ne...\n*33│| 14 | **Stream lost mid-context-bundle-sync** (responder dies / wire tears before `Done`) | That one pull errors at the requester | The requester's context store (an incomplete bundle is never fetched — length-gated, scratch file deleted); the responder's store untouched | No resume protocol by design: re-pull fresh — the apply is ancestry-idempotent (an already-joined tip short-circuits; fetched tips quarantine under `refs/spt-sync/` so a partial fetch never clobbers `refs/heads/`) | `tests/sync.rs...\n 34│| 15 | **Partition: concurrent context writes on two nodes** | The file's auto-propagation pauses (conflict surfaced, not propagated) | BOTH versions — local file untouched on each node + the other's version as a durable tracked artifact (hazard 6.6); every other file keeps syncing | Elected reconciler (Active instance's node; lowest-id fallback) runs one bounded Psyche turn → merged write `join(vA,vB)+bump` dominates both parents → propagates as a plain accept, clears artifacts subnet-wide | `test...\n 35│| 16 | **Reconcile turn fails** (harness absent / timeout / garbage or empty output / 6.5-suppressed write) | That file stays in conflict-holding state | Both versions (artifacts + local file) — exactly where they were; the store, the sync loop | Retry at the next sync/activation; a fresher direct write re-merges against the new local version | `spt-live reconcile.rs::failed_turn_preserves_everything` / `suppressed_write_preserves_artifacts`; `turn.rs::empty_stdout_is_an_error` |\n 36│| 17 | **Registry ambiguity during reconciler election** (partition: two nodes both see Active / no Active anywhere) | Two textually-different merges of the same pair may both mint | Neither version is ever lost — merged writes carry joined vectors, so the two merges classify **concurrent** and re-surface as a new conflict pair (detected, not silently last-wins) | Deterministic tiebreak bounds the storm (lowest node id among Actives / non-Offline holders); the re-surfaced pair reconciles on the next pa...\n   │...\n 57│| P3 | **Relay unreachable** (n0 outage / air-gap) | WAN dials needing relay fail; LAN/direct unaffected | Everything local | D9 two-host |\n*58│| P4 | **PTY broker drain thread dies** | That session's output stalls | Other sessions, net side | with terminal-wrapper hardening |\n 59│| P5 | **Manifest invocation hangs** (adapter subprocess) | That endpoint's lifecycle op | Daemon scheduler (KH 5.3 timeouts + 7.4 non-blocking) | when daemon hosts N agent loops |\n 60│| P6 | **Registry feed flood** (malicious/buggy peer spams updates) | Bounded by ring caps + lease; CPU cost unmeasured | — | D9 + REQ-SEC-1 outer gate |\n 61│| P7 | **Closed-watcher starvation** (net runtime saturated) | Presence lags behind reality | Conn table eventually consistent | D9 chaos pass |\n## FLAKE-LEDGER.md\n 17│| 8 | CI build step `Build notify-shell` (kitsubito Linux) | 1× kitsubito (2026-06-16 run 27652755792 attempt 1, v0.8.1 PR #17) | crates.io dep-download blip during the notify-shell build — `download of config.json failed, curl failed` fetching `serde_json`: a transient registry/network fetch failure on the runner, not a code or test fault (a real dep break fails both runners deterministically; this hit one runner once) | observed; cleared by `gh run rerun --failed` (attempt 2 green, builds reused). H...\n*18│| 9 | `spt dummy_harness_e2e` (BOTH tests: `endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches` + `endpoint_run_attach_awaits_online_before_attaching`) | 1× kitsubito (2026-07-05, REMOTE-TRUTH Batch-2 re-gate @f2a799e; roles swap run-to-run) | **process-global `set_var` race in a multi-test e2e binary.** Both tests `std::env::set_var(\"SPT_HOME\", own_tempdir)` then do IN-PROC reads (`perch::spt_home`/`adapters_dir`, `registry::register`, psychebin `fs::copy` staging). One file = one test b...\n*19│| 10 | `spt-store registry::tests::concurrent_registration_never_locks` | 1× hfenduleam local gate (2026-07-05, REMOTE-TRUTH Batch-2 bless matrix @3f6f327, fresh worktree, `nextest --workspace --no-fail-fast`) | 16-concurrent-writer SQLite registration gate FAILED at 5.63s — the SQLITE_BUSY load-contention class ALREADY documented at `db.rs:60`: box saturated by the 1588-test full-parallel suite meant a writer couldn't schedule its retry inside the default 5s `registry_busy_timeout`. Isolated ×5: gre...\n 20│| 11 | `spt-daemon::resume resume_mode_brain_spawning_new_sessions_delivers_each` | 1× kitsubito (2026-07-05 run 28768663321 attempt 1, REMOTE-TRUTH [twohost] PR #52) | Phase-B heavy-class timing EOF — \"session exited before the expected output arrived\" (UnexpectedEof) at 62s under the serialized heavy pool. SAME FAMILY as this file's prior Linux-CI flake (7940318, D4-2b: sequential spawn/read hardening) — a spawned session's exit raced the expected output read on the slow shared box. File untouched...\n*21│| 12 | `spt-daemon::input_ack_deadlock input_flood_through_serve_attach_does_not_deadlock_broker` | 1× hfenduleam local gate (2026-07-06, REMOTE-TRUTH F-2 matrix @a21bc6b, `nextest --workspace --no-fail-fast`) | deadline-margin under full-suite load: the flood exchange's `recv_timeout(30s)` (input_ack_deadlock.rs:512) blew under the 1608-test parallel pool + 7 leaky daemons — FAIL at 34.5s vs ~32s natural runtime isolated (the margin is ~3s by design). Subsystem disjoint from the triggering commit (F-...\n 22│| 13 | `spt::bin cli::tests::probe_all_cap_batches_into_windows` | 1× hfenduleam local gate (2026-07-06, stack matrix @0f08fa9 leg-3 rerun) | fast unit (0.52s natural) FAILED at 1.7s under the 1614-test full-parallel pool — probe-batching window assert with a timing component starved under load; subsystem disjoint from the triggering stack (F-2 reporting / E-2 grid title / picker rider). Isolated ×5: green, 0.52–0.55s. Same night as #12 — the full-parallel local matrix under a busy box is the com...\n 23│| 14 | `spt::bin rc::tests::attach_viewport_reconnects_across_a_broker_bounce` | 1× hfenduleam local gate (2026-07-06, WORKER-TRUTH W-3 build @worker-truth, `nextest -p spt --bin spt` full-parallel 360-test run) | **TIMEOUT, not an assertion fail** — SLOW>60…>TERMINATING at the 240s cap under the 360-way `--bin spt` pool. A REAL-broker + broker-bounce reconnect UNIT test (rc.rs:2071) that escaped the HEAVY-class serialization: the Phase-A/B split + `heavy-broker-pty` test-group target integration te...\n## GATEWAY-LIVENESS-DISPATCH.md\n 12│\n*13│## Key source facts (already traced)\n 14│- `advertised_status` (registryhost.rs:822-833) DOES feed `effective_rest_state` the pid-fallback `is_perch_alive` (line 825). So the DAEMON's advertised value should be Active for a pid-alive gateway — the divergence is NOT the advertise computation.\n 15│- So the `--json` Suspended comes from the projection/rendering path dropping the pid-fallback the human local-roster applies. **Candidate sites to pin (STEP 1):**\n 16│  1. `resource_projection` (spt-net registry.rs:566) — does it re-derive/filter liveness (e.g. `is_registry_entry_alive`, status-based: liveness.rs:152, which for a status-ABSENT row = not ONLINE||UNBOUND → stale/offline) and DOWNGRADE the row to Suspended/offline, ignoring the advertised Active? (its test `resource_projection_filters_hidden_and_offline` @registry.rs:1821 says it DOES filter on liveness.)\n   │...\n 19│- **DEFECT A (separate, CONFIRMED, stands):** `establish_perch`'s build (spt/src/api/startup.rs:214-302) carries `cwd/controllable/adapter/read_env` forward from the prior record but NOT `rest_state` → a re-bind writes a fresh InfoJson that WIPES the wake intent (flynn's tick11 rest_state:active vanish).\n*20│- **DEFECT B (RECLASSIFIED to LATENT — do NOT make it the primary fix):** the `status=online` write in `cmd_listen` (startup.rs:486-491) is gated on `live_capable` (manifest `[session.psyche_init]`); a gateway has no psyche_init so it never writes status=online. BUT the pid-fallback is DESIGNED to cover an absent status and DOES for the roster + advertised_status. The operator's ready_agent counterexample proved this: ready_agents work because claude-spt declares psyche_init (live_capable-by-manifest),...\n 21│\n 22│## The fix (after STEP-1 pin)\n 23│**Reader parity:** `endpoint list --json` must report the SAME status for a locally-hosted endpoint as the human `endpoint list` does — i.e., honor the pid-fallback / local-owned truth (`roster::enumerate` / `reconcile_self_owned`) so a machine consumer never sees a different liveness than a human. A pid-alive locally-hosted gateway must read the same (ONLINE/Active) on both surfaces.\n## INJECT-SETTLE-REARM-GATE-VERDICT.md\n  7│\n *8│**Root:** `run_inject_worker` gated the W5-A Layer-1 settle-gate behind a worker-local ONE-SHOT (`settled_once`) on a FALSE premise (head-swallow race is startup-only). A mid-session `/clear` re-enters the harness raw-mode input reader → re-creates the pre-settle window, but the one-shot already fired at spawn → `settle_before_inject` skipped → head eaten again; echo-verify (Layer 2) default-OFF for that session → silent + unrecoverable.\n  9│\n 10│**Fix:** `settled_once` one-shot → `probe_unobservable` latch + pure `should_settle(attempt, probe_unobservable) = attempt>1 || !probe_unobservable`. Re-settle before EVERY delivery on an OBSERVABLE (echoing/interactive) PTY (bug-prone class, cheap); latch-skip the steady-state settle ONLY where the probe is UNOBSERVABLE (non-echoing ConPTY — no reattach race, full-deadline cost). The settle's own bool return discriminates the class, latched on attempt 1; a re-drive always settles. Mints REQ-HAZARD-I...\n 11│\n## KNOWN-HAZARDS.md\n  11│### 1.1 Phantom INIT_SIGNOFF after grace period\n *12│- **Failure:** orphan teardown enqueues INIT_SIGNOFF before the grace-period recheck; a transient Self recovery (binary handoff, brief stale poll) makes the recheck pass-as-alive, but the signoff was already spooled and drains on the next iteration → teardown despite a live Self.\n  13│- **Invariant:** grace-period wait MUST complete *before* composing/delivering INIT_SIGNOFF; the recheck must bind `still_gone` before any envelope write.\n  14│- **spt-core mapping:** in-daemon now (no separate wrapper), but the ordering invariant is identical — orphan/teardown logic must re-evaluate liveness after the grace wait, not before enqueue.\n  15│- **Sister cite:** `src/live/wrapper/orphan.rs:201-259` (sleep@209 precedes compose@231-251); tests T-grace-recovery:576, T-still-gone-recheck:618.\n    │...\n  29│### 1.4 Deferred spool rows must not leak to the event stream\n *30│- **Failure:** a hook spools a deferred (spool-only, no TCP wake) notice; startup `drain_all` flushes ALL rows including deferred → event emitted at wrong time/priority.\n *31│- **Invariant:** startup drain (and idle/timeout TCP-wake sites) use `drain_non_deferred` only; deferred rows are picked up by their intended consumer via `peek`. All drain sites must agree on which rows they flush.\n *32│- **spt-core mapping:** carries directly — the daemon's spool-drain has the same deferred-vs-immediate distinction.\n *33│- **Sister cite:** `src/owl/poll.rs:276-316`; `spool::drain_non_deferred_with_metadata`.\n  34│\n  35│### 1.5 Worker (working-perch) lifecycle path consistency\n  36│- **Failure:** subagent-start creates the perch at one path layout; later hooks read it at another → not found; stop-hook scan misses nested perches.\n    │...\n  71│- **Failure:** the sister evaluates Psyche/perch liveness via a dedicated process PID — the wrapper's own pid in `info.json`, checked with `is_process_alive`. Under ADR-0004 the Psyche (and any spt-hosted Self) is a **loop inside the daemon**, not a separate process: it holds no dedicated pid, and its `claude`/summarizer subprocess is ephemeral (spawned per pulse/commune, then exits). If a daemon-hosted perch's `info.json` carries the **daemon's** pid, then *every* hosted endpoint shares one pid, and `...\n *72│- **Invariant:** for **daemon-hosted** perches (Psyche; spt-hosted Self), liveness is the **daemon's authoritative in-memory endpoint table + a `status` field** on `info.json` (`online|offline|…`), **never** `is_process_alive(info.pid)`. `info.pid` for a daemon-hosted perch is at most a *hosted-by-daemon* marker (the daemon pid), not a liveness signal; registry stale-clean for these rows keys on the daemon's endpoint table, not per-row pid. This reuses the pattern already specified for **Shells** (`inf...\n  73│- **spt-core mapping:** the **M1/M2a interim** model keeps the Psyche/listener a real per-process owner (the `api listen` process), so its per-pid liveness (`deliver::is_online` → `info.read_pid` → `proc::is_process_alive`; `registry::clean_stale_entries`) is correct *interim*. **M3 daemon consolidation replaces it** with daemon-authoritative liveness for hosted perches. Keep the liveness check behind one resolver (mirrors `resolve_address` stale-clean) so the M3 swap is localized — do **not** let ...\n  74│- **Sister cite:** `src/live/wrapper/orphan.rs` (wrapper-pid liveness); `src/common/list_filter.rs:168-175` (pid-classify); spt-core `crates/spt-store/src/{proc.rs,registry.rs}` + `crates/spt-msg/src/deliver.rs::is_online`.\n  75│\n    │...\n  81│- **Failure:** `ring` creates an ephemeral perch; early-exit paths (no-perch, empty-msg, timeout) skip cleanup → stale dirs accumulate.\n *82│- **Invariant:** every code path that creates an ephemeral perch cleans it before exit; exception: if the caller already had an active perch, do not treat as ephemeral and do not clean up.\n  83│- **spt-core mapping:** `ring` semantics carry; the daemon owns ephemeral-perch lifecycle, so a single guaranteed-cleanup (drop guard / RAII) is achievable in-process.\n  84│- **Sister cite:** `src/owl/ring.rs:58-294`.\n  85│\n    │...\n 120│\n*121│### 4.4 Deferred rows survive poll drain\n*122│- **Failure:** poll `drain_all` flushes a deferred (spool-only) message meant for a hook consumer → message lost.\n*123│- **Invariant:** deferred rows are never flushed by the event-stream drain; only `drain_non_deferred_*` / `peek_all` touch them.\n 124│- **Sister cite:** CHANGELOG v1.11.20; `src/common/spool.rs`. (See also 1.4.)\n 125│\n 126│### 4.5 Inbox legacy compat must not double-deliver\n    │...\n 128│- **Invariant:** spool is the sole read path at poll time; inbox is write-for-compat only and never read.\n*129│- **spt-core mapping:** clean-room — likely drop the legacy inbox entirely. If kept for any compat, preserve \"never read at drain time.\"\n 130│- **Sister cite:** `src/common/inbox.rs`.\n 131│\n 132│### 4.6 Addressable-id charset reserves the address delimiters\n    │...\n 134│- **Failure:** a bare endpoint id that contains `:` or `@` (or a path separator / whitespace / control char) makes the canonical qualified address `[subnet:]id[@node]` (ADR-0006 / REQ-INST-10) ambiguous to parse, and lets a name smuggle into a perch directory path. Once permissive ids exist in the wild, tightening later needs a migration.\n*135│- **Invariant:** every addressable id/name is validated to `[A-Za-z0-9_-]` + Hiragana/Katakana/CJK only, length `1..=64`, **at every creation seam** (`ready` start, `api bind`, `api listen`, `api worker-start`). `:` and `@` are permanently reserved as address delimiters; reads of existing perches are never re-validated. Enforce now (pre-M3/M4) so no permissive id-data accumulates.\n 136│- **spt-core mapping:** `spt_proto::id::validate_endpoint_id`; called at the four creation seams. The existing Psyche (`<parent>-psyche`) / Worker (`<parent>-w<N>`) suffix scheme uses only `-` + alphanumerics, so composite ids validate.\n 137│\n 138│### 4.7 Concurrent SQLite openers must not fail with \"database is locked\"\n 139│<!-- [doc->REQ-HAZARD-REGISTRY-CONCURRENT] -->\n*140│- **Failure:** two endpoints on one machine open the same SQLite store at once (e.g. two `ReadyAgent::start` calls registering simultaneously) and one fails outright with `SQLITE_BUSY` / \"database is locked\" → spurious registration/spool failure. Surfaced as a parallel-test flake in `two_agents_exchange_message_tcp_and_spool`, but the bug is real concurrency, not test-only.\n 141│- **Invariant:** `busy_timeout` is set **before** any lock-taking statement on every connection. Switching `journal_mode=WAL` takes a brief exclusive lock; with the default 0ms timeout it fails immediately under contention, so the pragma order is load-bearing: `Connection::open` → `busy_timeout` → `journal_mode=WAL` → `CREATE TABLE …`. WAL alone is insufficient (concurrent *writers* still serialize; they must *wait*, not error).\n 142│- **spt-core mapping:** `spt_store::registry::open_registry` + `spt_store::spool::open_spool_at`; both set `busy_timeout=5000` first. Any future SQLite store (history Path B, instance registry) must follow the same ordering.\n 143│\n    │...\n 153│- **Invariant:** every SQLite store's open path `create_dir_all`s its parent dir itself, best-effort, before `Connection::open` — never relying on another subsystem having materialized the home first. (Mirrors the spool, which always did this; the registry didn't.)\n*154│- **spt-core mapping:** `spt_store::registry::open_registry` (`create_dir_all(owlery)` before open). `spt_store::spool::open_spool_at` already creates its perch dir. Any future SQLite store must do the same — pair this with the 4.7 pragma ordering on every new store.\n 155│\n 156│### 4.10 Dead node identities leave immortal registry rows  `[REQ-HAZARD-REGISTRY-GHOST-ROWS]`\n 157│<!-- [doc->REQ-HAZARD-REGISTRY-GHOST-ROWS] -->\n    │...\n 201│- **Failure:** a broker reading a ConPTY master sees only the 4-byte startup query `ESC [ 6 n` and then nothing — the child looks hung/silent but is producing output normally. ConPTY blocks all child stdout until the terminal answers the cursor-position query.\n*202│- **Invariant:** every ConPTY reader auto-answers DSR (`ESC [ 6 n` → write `ESC [ 1;1 R`, or a real cursor position) on the PTY writer. Secondary: a ConPTY master does not EOF while the writer is held, so read loops drain on a thread and never gate exit on a blocking `read()`.\n 203│- **spt-core mapping:** `spt-term` broker PTY reader (ADR-0004). Brand-new to spt-core — not in the sister project (it never hosted ConPTY directly).\n 204│- **Source:** Spike #1 (`docs/spikes/SPIKE-01-broker-handoff.md`); reproduced with both a Rust child and `cmd.exe`.\n 205│\n    │...\n 222│- **Failure:** the daemon runs DETACHED (no console, 5.6/`detached_no_inherit`). Any console-subsystem child it spawns (`git`, `taskkill`, manifest hook commands) gets a **fresh conhost with a visible window** — piped/null stdio does NOT prevent it. Field shape: the 60s sync pump's two git calls (`for-each-ref` + `rev-parse`) flashed two blank windows per minute on the user's desktop (2026-06-06).\n*223│- **Invariant:** every short-lived console child spawned from daemon-reachable code sets `creation_flags(0x0800_0000)` (`CREATE_NO_WINDOW`). Long-lived detached children use `detached_no_inherit` (already `DETACHED_PROCESS | CREATE_NO_WINDOW`); de-elevated spawns use `CREATE_NEW_CONSOLE + SW_HIDE` (5.7 — `CreateProcessWithTokenW` rejects `CREATE_NO_WINDOW`, error 87).\n 224│- **Test seam caveat:** window-absence is unobservable from a consoled test runner — the child inherits the runner's console and never creates a window, flag or no flag. Unit coverage asserts the flagged spawn still works (the error-87 \"flag combo breaks spawn\" regression class); window-absence was verified live by process-watch capture.\n*225│- **spt-core mapping:** `spt-store::gitrun::run_git` (every BranchStore/ContextStore git call), `spt-daemon::shellhost::kill_shell_pid` (taskkill), `spt-runtime::run_bounded_command` (manifest hook commands), `spt-runtime::ManifestRuntime::command_for` (the one shared builder behind `spawn_session` + `run_bounded_stdin` — the notif pump's `spawn_notif_command` and the live agent's psyche/echo/turn spawns), `spt-daemon::shellwake` (already guarded). The flag lives in each shared builder, not per call si...\n 226│- **Source:** spt-core field bug, 2026-06-06 — two blank windows flashing every 60 seconds on a desktop workstation, caught by process-spawn watcher (git.exe parent=spt daemon, conhost.exe child each).\n 227│\n 228│### 5.9 `Instant - Duration` underflow-panics on a freshly-booted host  `[REQ-HAZARD-INSTANT-UNDERFLOW]`\n    │...\n 243│### 5.11 Self-elevating re-launch must re-run verbatim, never widen / inject / loop  `[REQ-HAZARD-SELF-ELEVATE]`\n*244│- **Failure class:** a privilege-gated command (`subnet create` / `join` / `show-code`) self-elevates by re-launching itself with privilege (Windows UAC `runas`, Linux `pkexec` / a terminal-emulator `sudo`, or inline `sudo`). A careless re-launch is a security hole: widening the privilege scope (adding args), resolving the binary by a bare name (a PATH/`secure_path` hijack runs an attacker's `spt`), interpolating a crafted arg into a shell string (`sh -c \"… $id …\"` injects a second command), or re-el...\n*245│- **Invariant:** self-elevation re-runs the **EXACT** original invocation with the binary's **ABSOLUTE** exe path — never adding/altering args, never a PATH-resolved bare name, never a shell-interpolated string. Every launcher passes an **argv array** (`Command::new(prog).args([...])`, never `sh -c`); the Windows `ShellExecuteW` params string (which is inherently one string) MSVC-quotes each verbatim arg so `CommandLineToArgvW` round-trips it as a single token. The elevated child drops state back to th...\n*246│- **Test seam caveat:** the real launch needs a UAC/polkit/sudo prompt (not hermetic) — manual-verify. The deterministic guards are the pure `decide_elevation_path` matrix (loop-safety: `AlreadyElevated` on every os; the os×env path order) and the argv builders (`sudo_argv` / `pkexec_argv` / `terminal_argv` assert absolute-exe + verbatim args + array; `windows_runas_params` asserts MSVC-quoting with no `cmd /c` interpolation; the crafted-arg test asserts a shell-metachar arg stays one element / one qu...\n 247│- **spt-core mapping:** `spt::elevation::{decide_elevation_path, sudo_argv, pkexec_argv, terminal_argv, windows_runas_params, print_hint_command, ElevatePath}` (pure), `spt::cli::{try_auto_elevate, launch_uac_window, pause_elevated_console_if_fresh, program_on_path, first_terminal_emulator}` (impure launchers) wired into `cmd_subnet_create` / `cmd_subnet_join` / `cmd_subnet_show_code`. Companions: 5.10 (the Unix abs-path-under-sudo facet) and 5.7 (the elevated child's de-elevation drop, which this compos...\n 248│- **Source:** M12-W4 design (subnet QR + self-elevating window), doyle ruling `M12-W4-RULING.md` Q6 — a privilege-escalation feature carries a mandatory hazard REQ.\n 249│\n## MANIFEST.md\n  29│min_spt_core_version = \"1.0.0\"     # readable before any update; compat gate\n *30│hostable_types = [\"LiveAgent\", \"ReadyAgent\", \"Psyche\", \"Worker\"]\n  31│shortcut_basename = \"cc\"           # optional; brands the picker's launcher → cc-<id> (default \"spt\")\n  32│host_binaries = [\"claude\"]         # optional; the harness exe(s) this adapter hosts agents inside\n  33│```\n    │...\n 123│<!-- [doc->REQ-PSYCHE-EPHEMERAL-DRIVER] -->\n*124│**Psyche contract — a bounded per-event turn (since v0.25.0).** A Psyche is **not** a resident process the daemon keeps alive between events. Declaring `[session.psyche_init]` is a **go-live gate only** — its presence promotes the endpoint to a LiveAgent, but spt-core **never spawns** it. Each psyche-relevant event (a pulse fire, a commune/signoff drop, a session-custody transition) instead runs **exactly one bounded turn** through **`[session.psyche_resume]` — the sole driven psyche role** — spa...\n 125│\n 126│<!-- [doc->REQ-PSYCHE-SID-CUSTODY] -->\n 127│**Custody sid — `{session_id}` is the Psyche's OWN id.** In a psyche role template `{session_id}` is the **Psyche's own minted session id**, kept in its nested `<parent>-psyche` perch record — **not** the parent's. A parent boundary (`/clear`, `/compact`) rotates the *parent's* sid but does **NOT** rotate the psyche sid: the Psyche's conversational thread survives parent resets (that is its job). When a template still needs the parent's sid it takes the **explicit** `{parent_session_id}` key — neve...\n    │...\n 132│<!-- [doc->REQ-PSYCHE-NESTED-RESOLUTION] -->\n*133│**`{subnet}` — the endpoint's home-subnet label.** A single `{subnet}` key fills to this endpoint's home-subnet label (`local` when unhomed), supplied whenever it is known so a psyche turn need never resolve a `--subnet` it cannot know; when no subnet is known the key is left unfilled so a referencing template fails **loud** (the `{node}` precedent). There is deliberately **no `{home}` key** — the endpoint home subnet is one concept, and `home:` is already the subnet-name qualifier position, so a `{h...\n 134│\n 135│<!-- [doc->REQ-PSYCHE-SID-CUSTODY] -->\n 136│**Reseed — reserved exit code 95.** When the Psyche's own session is lost or invalid (first host, store wipe, harness-side expiry) the adapter signals it with the **reserved exit code 95**; spt-core clears the stored custody sid and drives the next turn as a fresh preload (a non-empty `{psyche_context_file}`), emitting a loud `PSYCHE_RESEED:<id>` marker so a custody-loss loop is visible — the failure budget above bounds it.\n    │...\n 169│|---|---|---|\n*170│| `{adapter_dir}` | the registry record's `source_dir` — the adapter's **install dir** (`adapters/_github/<safe>/` for a `--release`/`--github` adapter; the copy-mode `source_dir` otherwise) | the same dir bare-program resolution already uses (REQ-INSTALL-11); **survives updates** (a `gh_release` re-extract re-registers in place), so a path built from it stays valid across `spt adapter update` |\n 171│| `{adapter_name}` | the resolved `[adapter].name` | identical to the catalog `{adapter_name}` above |\n 172│\n 173│Because they carry no session/event context, an adapter can build a path to its **own packed binary** and have spt-core *resolve* it without spt-core ever executing it — spt-core substitutes and **returns** the string; the adapter's own wrapper runs the result (resolve-not-execute; see ADR-0029). The canonical use is hook dispatch: an adapter stores a command template in `[strings]` (below), reads it once per session with `spt adapter get-string`, and runs the resolved command per-hook from a thin stat...\n    │...\n 490│<!-- [doc->REQ-ADAPTER-UPDATE-MESSAGE] -->\n*491│**Post-update notice — `message`.** The optional **`message`** is a plain (multi-line) human notice the adapter surfaces to stdout **only when `spt adapter update` actually applies an update** (the version changed) — never on a no-op / up-to-date run. It is read from the **newly-installed** manifest and rendered through the same inline-Markdown prose path as `spt`'s help (`**bold**`, `` `code` ``, `[text](url)`); there is no `{key}` substitution. It is avenue-agnostic (`gh_release` / `delegated` / `f...\n 492│\n 493│<!-- [doc->REQ-ADAPTER-UPDATE-POST] -->\n 494│**Composite update — `[update.post]` (since v0.16.0).** An optional **avenue-agnostic** sub-table that runs a delegated **post-step** *after* the primary update avenue resolves, in the same `spt adapter update`. It lets an adapter pull its `.spt` from `gh_release` **and** run a second, adapter-owned step (e.g. an in-harness plugin sync) under one lever.\n    │...\n 506│\n*507│- **Runs unconditionally** — the post-step fires **even when the primary avenue was a no-op** (already up to date). Its own idempotent check (e.g. `claude plugin update`) decides whether anything changes. `command` is **required** and **non-empty** (an empty `command` is refused at registration). `self_verifies` is **attestation-only metadata** (defaults `false`) — parallel to `[update].self_verifies`; it records that the post-step verifies its own outcome but **gates nothing** (the post-step is alre...\n 508│- **Published stdin seam** — spt-core feeds the post-step **one JSON line** on stdin describing the just-resolved update:\n 509│  ```json\n 510│  {\"adapter_applied\": true, \"adapter_name\": \"claude-spt\", \"profile_name\": null,\n    │...\n 527│\n*528│**Trust — optional signing, fail-closed.** With no `signing_key`, the fetched `.spt` is trusted on HTTPS + GitHub (the same first-acquisition trust `spt adapter add --release` and the installer's first binary fetch place). With a `signing_key`, the fetched `.spt` is verified against a **detached signature** the author publishes as a sibling release asset named `<asset>.sig` — a lowercase-hex Ed25519 signature over the raw archive bytes. Verification runs **after** the bytes are staged and **before** ...\n 529│\n 530│Validation requires `repo` for `gh_release` (`asset` and `signing_key` optional). `spt adapter add` reuses this section as the **install** mechanism (install is the first update; `--github <user/repo>` fetches the manifest first, then installs via the declared `avenue`). `uninstall` (optional) is its inverse, fired by `spt adapter remove` (soft-deregister; runs when no instance is live under the adapter, or immediately with `--force`). Absent `uninstall` ⇒ spt-core's default cleanup (drop its copy for ...\n 531│\n## MSG-IDENTITY-DISPATCH.md\n 50│hidden `spt spool-audit --json`) — use it; do not rebuild it.\n*51│- `REQ-LIVE-AGENT-NO-INJECT-DELIVERY` [impl,unit] (F-033) — root ALREADY code-grounded:\n 52│  `is_spt_hosted_no_relay` (inject.rs:37-49) treats a controllable live_agent with no relay\n 53│  address as inject-target → duplicate + stuck-unsubmitted input garbage. Fix = exclude\n 54│  state:live_agent from the inject predicate (perri confirmed core-only). Buildable immediately.\n   │...\n 56│  send to BUSY doyle delivered via BOTH poll AND relay (spool row 156 `taken_leg='idle-inject'`).\n*57│  Pin with spool-audit: poll-drain vs relay delivered=1 ordering; busy→idle edge re-offer;\n 58│  take-mark atomicity. Then: one atomic cross-carrier claim per row.\n*59│- `REQ-RELAY-NO-BUSY-DELIVER` [impl,unit] — companion: idle-edge drain re-verifies activity /\n 60│  defers to the claim. May REDUCE to an ordering assertion under the exclusive claim — doyle\n 61│  rules at RCA lock; don't build blind. **Do not break** F-023 anti-starvation +\n*62│  REQ-IDLE-PARKED-DELIVERY (already-idle delivery stays green).\n 63│- `REQ-SEND-STAMP-AGENT-ID` [impl,unit,int] — a live session's shelled `spt send` stamps the\n 64│  agent id, not `cli@<node>` (send-time self-resolve via the existing session-pin machinery).\n 65│  Identity leg; independent of the RCA — can build parallel.\n## MUSL-W1-DISPATCH-RULING.md\n 36│## Sequencing\n*37│W1 (this) → gate → **W2** (register musl: `current_platform()` musl cfg arm + the musl `PlatformSpec` row + asset map; DNS/crypto/build already proven) → gate → **W3** (CI musl build + sign + update-set publish + self-update E2E on a sub-2.39-glibc box) → gate → release (deployah publishes). doyle drives the whole arc.","truncation":{"content":"# crates/spt-daemon/src/\n## brainproc.rs\n 9|//! `spt daemon brain` child. The child connects over the existing broker socket\n*10|//! IPC, signals `ready`, hosts the brain-side logic loops, and idles. D2\n 11|//! migrates the broker-resident loops here one per commit; **D2-1 moved the net\n 12|//! consumers** (inbound dispatcher + outbound peer pump — pure IPC clients, no\n 13|//! shared `Arc<Broker>`) into the brain, gated on the existing `net-status`\n...\n 33|use crate::brain::{now_ms, Brain};\n*34|use crate::endpoint::{brain_ready_path, broker_socket_name};\n 35|use crate::relcache::{AppliedRecord, ReleaseCache};\n 36|\n 37|/// Supervisor backoff floor after a brain death (mirrors the peer-pump\n...\n 44|/// A run at least this long counts as healthy and resets the backoff floor.\n*45|/// Reused as the per-attempt **trial window** (ADR-0018 Q7, D6-2): a candidate\n*46|/// brain has this long to signal `ready` for its generation before the window\n*47|/// elapses (the \"up but useless\" alive-never-ready rollback path).\n 48|const SUPERVISE_HEALTHY_RUN: Duration = Duration::from_secs(30);\n*49|/// The readiness-trial retry budget (ADR-0018 Q7, D6-2): a candidate that\n*50|/// **exits before** signaling ready is respawned this many consecutive times\n 51|/// (a transient first-boot hiccup should not abandon a good binary) before the\n 52|/// supervisor auto-rolls-back to the last-known-good binary. Matched to the\n*53|/// capped-backoff cadence; either this budget OR an alive-but-never-ready\n 54|/// window-expiry triggers rollback.\n*55|const TRIAL_PREREADY_BUDGET: u32 = 3;\n 56|/// Poll/sleep slice so a `stop` request (and a child exit) land promptly.\n 57|const TICK: Duration = Duration::from_millis(200);\n 58|\n...\n 61|const BRAIN_CONNECT_TRIES: u32 = 300;\n*62|/// Cadence the idle brain refreshes its ready breadcrumb + probes the broker.\n 63|const BRAIN_HEARTBEAT: Duration = Duration::from_millis(500);\n 64|\n 65|/// Why the broker spawned this brain — the Q4 update-vs-crash discriminator the\n...\n 125|    /// Request a planned restart (broker handler side) — idempotent: coalesces\n*126|    /// with any already-pending request into one cycle.\n 127|    pub fn request(&self) {\n 128|        self.requested.store(true, Ordering::SeqCst);\n 129|    }\n...\n 155|///\n*156|/// It inherits the broker's already-unelevated token as a plain child, so it\n 157|/// runs no de-elevation guard of its own.\n 158|// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n 159|pub fn run_brain(generation: u64, reason: StartReason) -> io::Result<()> {\n...\n 192|    // CURSOR-ONLY resume (UPDATE-WEDGE round 3, REQ-BRAIN-RESUME-NO-CONN-DEADLOCK,\n*193|    // doyle Option A-split): the supervised daemon brain is a NON-DRAINING consumer of\n 194|    // this conn (the heartbeat below reads it only in net_status()/sessions()\n*195|    // drain-and-discard loops), so it MUST NOT subscribe — an undrained subscription on\n 196|    // this request/reply conn is the SharedSend self-deadlock that re-wedged v0.30.4\n 197|    // (brain.rs resume_sessions deadlock warning). `resume_session_cursors` re-establishes\n 198|    // the per-session dedup cursors + returns the ids WITHOUT subscribing; the subscribing\n*199|    // `resume_sessions` stays for DRAINING callers (the resume int tests / the forward\n*200|    // live-agent-adapter drained carrier). Empty today (the daemon hosts no PTY sessions).\n 201|    // [impl->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n 202|    match brain.resume_session_cursors() {\n 203|        Ok(ids) if !ids.is_empty() => {\n...\n 212|\n*213|    // Signal READY only now — AFTER session resume (restoration D6-1b, ADR-0018\n*214|    // Q7): \"ready\" is the design's \"re-attached all sessions + resumed loops\",\n 215|    // not merely \"socket connected\". The supervisor's readiness gate (D6-2)\n*216|    // promotes the trial binary on this stamp; promoting on a bare post-connect\n 217|    // breadcrumb would accept a brain that connected but never rebuilt\n 218|    // continuity. Today resume is a near-noop (no daemon-hosted sessions), so\n 219|    // this is forward-correct: the gate is defined on the real readiness\n 220|    // semantic the live-agent adapter inherits.\n*221|    write_ready(generation);\n 222|\n 223|    // Shell wake-watchers (restoration D2-2): the offline half of the shell\n 224|    // online/offline exclusivity — a reconcile loop that runs each offline\n\n# docs/\n## BROKER-BRAIN-SPLIT-RESTORATION.md\n 14|\n*15|**Two audit corrections folded in:** the NetHost is **already broker-owned** (`broker.rs:175` `OnceLock`, per the §B M4-D4a note), so the Q5 net item is near-free, not a migration; the supervise-backoff is wired at `peerloop.rs:805` (M8-D4), confirming Q7's reuse claim.\n 16|\n 17|**Six amendments raised and accepted** — each folded into the relevant section below and marked **[V1]**–**[V6]**.\n 18|\n...\n 24|\n*25|While verifying the v0.3.2 cross-OS update fix end-to-end on the real fleet, the third node (`enlyzeam`, a Windows box hosting the `gerald` ready_agent) was updated:\n 26|\n 27|- `spt update apply` returned `APPLIED:6`.\n 28|- The installed `spt.exe` on disk was the valid 0.3.2 Windows PE (magic `4d5a`, sha matched the published binary), `--version` reported `0.3.2`, `applied.json` = `{version:6}`, backup `spt.exe.old-6` present.\n...\n 55|- ADR-0004 Consequences: *\"A small internal broker **process** exists beneath the daemon — a deliberate, bounded walk-back of 'literally one process,' … guaranteeing endpoint survival across updates.\"*\n*56|- ADR-0004 rejected alternatives include **\"whole-daemon live FD-passing\"** and **\"drain + restart\"** — i.e. the chosen design is specifically *restart the brain, the broker process survives.*\n 57|- `M3a-PLAN.md`, `M3-PLAN.md`, `M3b-PLAN.md` all name \"the broker **process** + IPC (M3b).\"\n 58|- Spikes 01/03/04/05/06 each **PROVEN** with *two separate binaries* (`spt-spikes/spike-01-broker-handoff`): a PTY child + a live QUIC transfer survive 100× brain restarts, gapless and exactly-once. A spike that proves \"survives a brain restart\" presupposes the broker is a separate process.\n 59|\n...\n 78|\n*79|> **Glossary note already applied:** `CONTEXT.md` was sharpened during the session to state explicitly that there is **one broker per machine** (per `SPT_HOME`), *not* one per endpoint — a single broker hosts every endpoint's resources and is present even with zero endpoints online. This corrected a latent \"one broker per endpoint\" misconception and is load-bearing for decision Q2.\n 80|\n 81|---\n 82|\n...\n 106|**Alternatives rejected.**\n*107|- *A thin third supervisor process spawning both broker and brain* — adds a process for no invariant the broker can't already hold.\n 108|- *Brain as parent, spawning the broker* — incoherent: a brain restart would orphan or kill its own broker child, dropping endpoints.\n 109|\n 110|**Operator's challenge that strengthened this.** The operator initially held a \"one broker per endpoint\" model and correctly objected that liveness shouldn't live in something that only exists when an endpoint is online. The code shows one broker *per machine*, always present, so the objection's principle is satisfied and actually reinforces putting liveness in the broker.\n...\n 113|\n*114|**Decision.** `apply` verifies + swaps the binary on disk, then signals the brain to **snapshot and self-exit**. The broker — already the brain's supervisor — observes the exit and **respawns from the executable path (now the new binary)**; the new brain re-attaches.\n 115|\n*116|**Reason.** This reuses primitives that already exist. `apply_brain_only` already models exactly \"snapshot the outgoing brain, drop it — the crash boundary the broker already tolerates — re-attach.\" The broker must already supervise and respawn the brain for crash recovery, so an update is just a *planned* snapshot+exit handled by the existing respawn path, and \"respawn from the executable path\" naturally picks up the swapped binary — which is literally what `applyhost.rs:37` says happens \"at next ...\n 117|\n 118|**Alternatives rejected.**\n 119|- *Broker actively cycles the brain on command* — nearly identical, but puts cycle logic into the layer we want to keep dumb and stable.\n...\n 134|\n*135|**Current state that motivates it.** The pump cadences are already catch-up/idempotent (\"stagger from everything due now,\" `peerloop.rs:304`) and are restart-safe as-is. The pulse loop is **phase-relative** (`lifecycle.rs:486` `sleep_interruptible(pulse_period)`), so its phase resets on restart — this is the loop that needs converting to a disk-anchored deadline. Idempotent catch-up loops need no phase state at all; only phase-significant loops are converted.\n 136|\n 137|**Alternative rejected.** *Snapshot each loop's `Instant`/phase into the handoff message and restore it* — fragile (monotonic clocks don't cross processes), lost on crash, and incompatible with the model-B trigger (no brain→brain message exists).\n 138|\n...\n 141|**Decision.** Apply ADR-0004 §B's rule — *a resource is broker-owned iff a live consumer would lose continuity on a brain restart* — to every component that today shares the in-process `Arc<Broker>`:\n*142|- **Net bring-up + REQ-DAEMON-9 self-heal retry → broker** (the brain must not own net bring-up). *Audit correction:* the NetHost is **already** broker-owned (`broker.rs:175` `OnceLock`, §B M4-D4a), and net bring-up + the self-heal retry already live in the `daemon.rs` entry that *becomes* the broker entry — so this item is **near-free**, not a migration.\n 143|- **Digest hub → broker** for the live projections + subscriber sockets; **the parse that feeds it → brain** (pushes parsed digests over IPC).\n 144|- **Seed lock + liveness → broker** (Q2).\n*145|- **Every other brain→broker call becomes a versioned IPC verb** — in the two-process world there is no shared `Arc`, so each current direct call is a latent IPC verb on the same versioned contract that already carries `SPAWN`/`INPUT`/`NET_*`/`SESSIONS`.\n 146|\n*147|**Accepted consequence.** This enlarges the \"dumb kernel.\" That is already-precedented: ADR-0004:63 accepted exactly this when QUIC ownership moved into the broker — *\"the broker is 'stable' in update cadence, not in narrowness.\"*\n 148|\n 149|**[V6] N-1 compat is scoped milestone work.** Because the broker almost never updates but the brain updates routinely, the **steady state after every routine update is NEW brain × OLD broker.** The `classify` pre-swap handshake (`brain_ipc_version ≥ broker.min_compatible`) gates the *forward* direction, but each direct-call→verb conversion must hold that N-1 window across the *whole* verb surface. The milestone must include a **CI-real compatibility test — old-broker binary × new-brain binary acr...\n 150|\n*151|**Exception — shellwake stays brain-side.** The supervised `wake_command` watcher *children* are an exception to the §B continuity rule: they remain **brain-owned** and are re-reconciled from disk on brain start (the boot sweep at `daemon.rs:206-217` already does orphan cleanup).\n 152|\n 153|**Reason for the exception.** Strict §B would put the watcher children in the broker for continuity, but the operator judged the continuity loss acceptable: updates are rare, the brain re-reconciles watchers on start, and keeping watcher-child *supervision logic* out of the kernel keeps the broker narrower. The only cost is a brief window during a (rare) brain update where an offline shell could miss a wake — explicitly accepted.\n 154|\n...\n 156|\n*157|**Decision.** The broker tracks each session-subscriber's last-delivered cursor. On (re)start the new brain queries the broker for **all** hosted sessions and re-attaches each in **resume** mode. **Output is at-least-once** (the broker resumes from last-*sent*); **input/effects stay exactly-once** via the already-broker-owned `EffectJournal`. The explicit `BrainState` handoff *message* is **retired** for production — the new brain cold-starts and reconstructs continuity by querying the broker.\n 158|\n 159|**[V2] Generation custody.** Retiring `BrainState` would orphan the generation counter (KH 2.4: increments on every start/revive; `gen_start` always `= now()`). Custody moves to the **broker**: it owns the counter, increments it on **every** brain spawn (planned or crash), and hands `{generation, start-reason}` to the brain at spawn time via a **versioned argv/hello field** (KH-2.3 forward-compat, defaulted). Because the broker observes every respawn, this is strictly more reliable than passing it brain-...\n 160|\n...\n 162|- Two concrete gaps exist today: `applyhost.rs:239` re-attaches only `sessions.first()` (one session) and with `from_seq = 0` (re-replays the whole ring → duplicate output).\n*163|- Under the model-B trigger the outgoing brain is gone before the new one starts, so **no brain→brain message can be passed**; continuity must come from the persistent side. The broker already holds the per-session output ring (`broker.rs:73-143`); making it also track the delivered cursor lets a respawned brain resume gaplessly with **no disk writes** (respecting Q4) and **no handoff frame**.\n*164|- At-least-once output matches the *existing* terminal-stream contract: SPIKE-05 already established \"terminal-stream ≠ exactly-once transfer\" (a resize repaint reorders and duplicates the stream anyway). Exactly-once output would require a per-chunk ack protocol for a guarantee the terminal stream does not make; the thing that *must* be exactly-once (injected input/effects) already has the journal.\n 165|\n 166|**Alternative rejected.** *Add brain→broker acks so the broker resumes from last-processed (exactly-once output)* — cost without benefit, given the terminal-stream contract.\n 167|\n...\n 169|\n*170|**Decision.** If the new brain fails to reach a **ready** signal (re-attached all sessions + resumed loops) within a bounded number of boots / a healthy-run window, the broker **rolls back to the last-known-good binary** (the `spt.exe.old-N` backup), **quarantines** the bad version (no auto re-apply/re-fetch), and surfaces a loud consent-style notification. The broker holds **last-known-good + candidate** paths and chooses which to spawn (no file manipulation at failure time). Endpoints survive throughou...\n 171|\n*172|**[V1] Rollback-state-compat invariant (forward invariant, mint this milestone).** Auto-rollback spawns the **old** binary against durable state the **new** brain may already have written. This is safe *today* (a source audit confirmed zero state-migration code exists), but the first release that migrates a durable-state schema would silently break Q7's rollback. The invariant must be minted now, while it is free: **a brain must not irreversibly migrate durable state before ready-promotion** — equivale...\n 173|\n 174|**Reason.**\n*175|- The dangerous case is a *runtime* bug (new brain panics on boot), not the binary land (already rolled back at `applyhost.rs:167-170`) and not IPC mismatch (the `classify` handshake `brain_ipc_version ≥ broker.min_compatible` is checked pre-swap). A naive supervisor would crashloop the new binary: endpoints alive but logic dead — \"up but useless.\"\n*176|- The backup binary is already produced for exactly this, and the readiness+backoff machinery already exists — `peerloop.rs:82-91` has `SUPERVISE_BACKOFF_BASE/CAP` + `SUPERVISE_HEALTHY_RUN` (60 s healthy resets backoff), and `supervise_pump` is wired at `peerloop.rs:805` (M8-D4, verified). A bad update that bricks *logic* should self-heal back to last-good without a human, the same spirit as the boot-race self-heal already shipped.\n 177|- The two-phase record fixes a real existing bug: `applyhost.rs:176` writes `record_applied(version)` + `last-outcome=applied` **before** the brain handoff — i.e. before we know the new brain boots. (This is the optimistic `applied.json={version:6}` observed on `enlyzeam`.)\n 178|\n 179|**Alternative rejected.** *Bounded retry → halt and wait for manual intervention* — safer-feeling but leaves logic dead until a human shows up; inconsistent with the self-healing posture.\n...\n 182|\n*183|**Decision/observation.** The broker spawns the brain as a **child process** (`Command::spawn`) and they communicate over the existing local socket IPC. **No `exec` is required**, so there is no Windows/Unix divergence. ConPTY (Windows) and forkpty (Linux) handles stay broker-side and never cross to the brain. Spikes 01 and 04 already proved the handoff on both OSes.\n 184|\n 185|**Reason.** This is precisely the uniformity ADR-0004:25 sought when it rejected whole-daemon live FD-passing as \"hard and platform-divergent.\" Spawn-plus-socket sidesteps it entirely.\n 186|\n## CONTEXT-MEMORY.md\n 19|\n*20|1. **Default-to-project.** A fragment goes to project context unless it *passes the cross-project test* to be promoted to live. Promotion needs justification; demotion is free. The bias points against the leak.\n 21|2. **`current focus / next steps` is project-scoped** — the single biggest leak category. Route it to project unless the focus genuinely spans projects.\n 22|3. **Live holds a thin project roster, never project depth** — cross-project *awareness* travels; depth stays per-project.\n 23|4. **Commune-source asymmetry** (the structural fix): the live signal is strong only from **Self-authored communes**; **echo communes are project-centric** (they synthesize project-heavy session logs). So **echo communes are project-context-primary and live-context-conservative** — they make few/no live edits. This kills the leak at its source. Provenance is recorded on each block (`Source:`).\n...\n 91|\n*92|pi-agent-memory's **human-in-the-loop approval** (extract → review → approve, max-5-candidates) and **bash-mutation guardrails** fit its \"human curates\" philosophy. SPT's Psyche is **trusted to author its own mind autonomously** — so we adopt pi's *structure* (blocks, INDEX, bounded awareness, provenance, OCC) but **not** its approval gate. Controlled mutation is already covered by the drop-file single-writer rule (KNOWN-HAZARDS 6.4).\n## DEBUG-CONVERGE-PLAN.md\n 82|1. **Reuse the update pull/serve handshake** (`propagate.rs`). The serve leg\n*83|   already answers \"what do you have?\" during a pull. Add a *status-only*\n 84|   query (no fetch) that returns `{channel, applied_version, last_outcome}` for\n 85|   the requester's platform. Cheapest: it rides the existing update wire\n 86|   (`net/update.rs`) and the QUIC-handshake-identity origin rule (KH 7.5) gives\n 87|   the responder's identity for free.\n*88|2. **Registry/presence enrichment.** If a node already publishes its applied\n 89|   version + provenance into a registry row (the rollout metadata carries\n 90|   `git_commit`/version provenance), the coordinator can read convergence from\n*91|   rows it already syncs — no new round-trip. Verify whether\n 92|   `relcache`/registry persists \"applied version\" anywhere queryable; if not,\n 93|   prefer option 1.\n 94|\n## DEBUG-ROLLOUT.md\n 63|- `--build-current` builds and includes the coordinator's current platform.\n*64|- `--artifact <target=path>` adds an already-built artifact for another target,\n 65|  such as `x86_64-pc-windows-msvc` or `x86_64-unknown-linux-gnu`.\n 66|- `--version <u64>` overrides the local debug sequence. Use a higher value when\n 67|  recovering from lost local state.\n## DEFERRED.md\n 11|| PresenceChannel implementation (broker, dispatch/bind/thread) | endpoint types v1 | Depends on presence gossip + Shells + multi-instance routing; only the seams ship v1. **M5 ships presence *resolution* (gossip + most-recently-active API) — its future substrate; the endpoint stays deferred** (user decision 2026-06-04). <!-- [doc->REQ-PRES-1] --> **M5-D6 seam note:** the substrate now exists — the gossiped datum (`Instance.last_active_ms`, riding the registry's epoch lease + replication) and the one...\n*12|| OS-level input-activity presence signal (7a-A) | presence v1 | Per-platform, privacy-loaded; agent-interaction heartbeat covers the v1 goal. **Note:** distinct from the v1 broker *sensing* user input on a PTY it already holds (spt-hosted) — that is in v1 and is NOT OS-wide input monitoring | Opt-in enhancement after agent-interaction presence proves out |\n 13|| Instantiate-anywhere + consent gate | Instances V1-mid | Remote launch over network is additive once remote-drive of running instances works. Consent *model* is now designed (CONTEXT §Consent & security gates); **the framework (grant store + escalation + pre-consent flags) ships M5 with this capability id reserved-but-refusing** (user decision 2026-06-04) — the capability lands into it later. Defers with it: the remote-fork arm, cross-node shell spawn, `shell_wake_spawn_anywhere` *behavior* (grant s...\n 14|| Remote command execution on another node | off-node reach-back v1 | Highest-risk capability; needs the consent/security gate. Workarounds exist (message a local agent; interact with local agent directly). **M5 ships the framework with this capability id reserved-but-refusing** (user decision 2026-06-04) | Designed alongside instantiate-anywhere's consent gate |\n 15|| Shell-binary (and harness-binary) sandboxing | Shell concept v1 | A capability toolset bounds what the *agent* can ask, not what the *binary* can do with its OS perms. Baseline stance: running any adapter/shell binary = a disclosed, accepted trust risk for all spt-core users | If/when untrusted third-party shell/harness binaries become common enough to warrant OS-level sandboxing |\n...\n 26|| Trust-row prune verb (`spt subnet prune <node>` or similar): registry GHOST rows now decay (4.10), but the dead identity's TRUST rows persist by design (trust is a user decision) — they cost dead dials every pump tick and clutter status views. BIGNET carries two such rows (09ef831e, both sides) | Ghost-eviction fix (2026-06-06) | Trust mutation = security surface; wants the elevation gate + the M8 noun shapes | M8 CLI nounification |\n*27|| Elevated spt-hosted endpoints — a consented elevation satellite: the broker lives inside the always-unelevated daemon (KH 5.7), so spt-hosted children are unelevated by construction; there is NO silent unelevated→elevated path on either OS (that is UAC/sudo's whole point). An agent needing admin powers is harness-hosted-elevated today (works: TCP listeners + talk-down pipes). The spt-hosted form needs a per-grant CONSENTED elevation: UAC prompt / polkit spawns a small elevated PTY-host satellite ho...\n 28|| ~~AMBIGUOUS render~~ **SHIPPED 2026-06-06** (rode the ghost-eviction fix): resolution refusals render valid copy-paste targets at the wansend boundary (`render_refusal`) — labels preferred, key-prefix on label collision, `subnet:id` for cross-subnet | M7 acceptance run (user 2026-06-06) | Pure render fix at the wansend/CLI boundary; carries into the M8 nounification sweep | DONE |\n 29|| Post-join address seeding: the pairing ceremony holds a live, authenticated connection to the peer, but the peer pumps then re-find each other from scratch via id-only discovery (fresh node ids must propagate to n0 DNS first) — observed live as ~1 min to first `status --nodes` convergence after a join. Seed the pump's address cache from the ceremony connection (both sides) for instant first sync | M7 acceptance run (2026-06-06) | Convergence is correct, just slow on first contact; needs an addr-cache...\n 30|| Clock source priority for TOTP/rendezvous: query a well-known world clock (NTP) FIRST, fall back to local time — machines with skewed local clocks currently derive wrong steps/rendezvous tokens silently | M7 acceptance grill (user 2026-06-06) | Needs an NTP client dep + offset caching + offline fallback semantics; ±1 window covers small skew today | First field report of clock-skew join failures, or M8 polish |\n*31|| CLI nounification (M8 candidate; ratified shape from the 2026-06-06 grill): `spt endpoint` namespace absorbs fork/suspend/wake/shutdown/rename/stop/digest + `access` (per-endpoint store — NOT subnet-scoped) + `description` (ex-`resources` blurb; bare = show, `set` = author); `spt endpoint list [--local\\|--subnet <name>]` = merged listing, default all-subnets grouped by subnet, SELF endpoint pinned distinctly at top (bare `spt endpoint` = this list); agent HOT PATH stays flat (`send`/`ring`/`ready`/`w...\n 32|| ~~Node label for an endpoint-less node (REQ-SUBNET-3 gap)~~ **SHIPPED 2026-06-08** (option 2 — node-level presence datum): a peer with **zero endpoints** rendered as a bare key-prefix in `status --nodes` because `node_label` only rode endpoint `Instance` rows. FIX: a node-LEVEL carrier — `SubnetRegistry.node_labels` (node→label) + a `NodeLabelUpdate` feed record riding the existing registry replication stream as an untagged `RegistryFeedRecord` variant (instance bytes unchanged → mixed-version ...\n*33|| **Instant + never-seen peer hostname via pairing-time capture (REQ-SUBNET-3 follow-up; the deferred \"option 1\")**: the shipped node-level label (option 2) is GOSSIP-ONLY, so a peer's name is learned only from a pump round while it is alive. Two residual gaps remain after the eviction fix below: (a) ~1 gossip cadence to appear after a join (the joiner trust-pins by pubkey with `label=None`; the hostname arrives next pump round — enlyzeam↔kitsubito converged to `ENLYZEAM` after a cadence, fine but no...\n 34|| ~~Subnet-scoped liveness probe (REQ-SUBNET-5 detach gap)~~ **SHIPPED 2026-06-08** (REQ-SUBNET-5 int stage): `subnet status --nodes` showed a DETACHED peer as **online** because liveness fell to `Probe` → a raw transport dial by node-pubkey, which succeeded on the peer's still-bound daemon endpoint (one ALPN `spt-core/net/0` serves all subnets, so the dial was subnet-blind). FIX: a new `ServeProbeRecord` (spt-net `serveprobe`) + daemon `serveprobe` handler/requester ask the peer \"serving subnet X?\", a...\n 35|| **Subnet full-mesh visibility (PLANNED MILESTONE — `SUBNET-MESH-PLAN.md`)**: non-directly-paired members never see each other — a subnet is currently the PAIRING GRAPH, not a mesh (A↔B, C↔B; B offline → A and C invisible; even with B online, B doesn't relay). Root: pairwise TOFU trust (`trust.rs`) + own-rows-only gossip / no transitive relay (`peerloop.rs`, KH 4.10/7.5) + no member roster in the pairing seed transfer. FIX = member-AUTHENTICATED subnet data (seed-MAC and/or per-node signatures...\n 36|| `xtask debug-converge` — automated convergence watcher for debug rollout (the REQ-UPD-6 first-slice item ADR-0016 names): watch every expected debug-pinned reachable lab node until all report the target version applied, else a per-node timeout table (Offline / StagedAwaitingConsent / BlockedByBrokerResources / Rejected{reason}). Activates REQ-UPD-6's `int` stage. Full build plan: `docs/DEBUG-CONVERGE-PLAN.md` | REQ-UPD-6 first slice (2026-06-06) | The first slice ships manual \"Apply and observe\" (`DE...\n*37|| **Origin-source update bootstrap (`spt update fetch` — pull the latest signed release from GitHub into the existing verify→stage→apply pipeline)**: today update DISCOVERY is **peer-only** (REQ-UPD-1: a daemon pulls offers from roster peers via `request_update`; `propagate.rs`/`peerloop.rs`). There is NO origin fetch, so the **first node in a fleet — or any isolated/solo node — cannot get a new release without a peer that already staged it** (the fleet converges by gossip, but only *after* a m...\n 38|| **Test broker socket-bind hardening (CI-flake removal)**: `applyhost` tests' `served_broker` helper does `Broker::bind(name).expect(\"bind broker\")` (`crates/spt-daemon/src/applyhost.rs:293`) where `name` = `spt-daemon-d7b-{pid}-{seq}.sock`. Flakes intermittently on the **kitsubito** self-hosted Linux runner (likely AF_UNIX path collision / stale socket file under the runner's reused workspace — Windows named pipes don't hit it). Cost the v0.3.2 release a full red CI run + a `--failed` rerun (2026-06-...\n 39|| **`spt update fetch` single→set shape-upgrade at same version (REQ-UPD-8 transitional trap)**: `cmd_update_fetch` sets the rollback floor = `staged_version` (the monotonic counter), and the gate is a plain `candidate > floor`. A node that fetched+staged version N as a platform **single** (pre-0.3.2 fetch behavior) can never re-fetch version N as a multi-platform **set** — same counter → `Rollback{current:N, candidate:N}`. Bites the **seed-node bootstrap**: the first node to origin-`fetch` a relea...\n 40|| **On-disk `current_exe` reconcile after auto-rollback (the D6-2 reboot residual)**: readiness-gated auto-rollback (ADR-0018 Q7, restoration D6-2) is **record-driven selection** — after a rollback the broker's supervisor keeps spawning the last-known-good `.old-N` binary, chosen from the durable `RolledBack` record, so the rescue survives a reboot for free **with no file rename at the failure instant** (the brittle path the design rejected). But the bytes on disk at `current_exe` are STILL the quarant...\n 41|| **Quarantine operator override (`spt update apply --force` — the note-i escape hatch)**: D6-2's quarantine guard refuses re-applying a version that auto-rolled-back on this node (the `RolledBack` record is the marker; clears naturally when N+1 stages). But a version whose failure was **environmental** (a transient boot condition, not bad bytes) leaves the node **permanently allergic** to a genuinely-good version with no escape but staging a newer one. FIX = an explicit operator override (`spt update ...\n*42|| **✅ RESOLVED — shipped v0.8.3 (REQ-HAZARD-BROKER-QUIC-DEADLINE, KNOWN-HAZARDS 7.8).** ~~Broker-side bound on brain-waiting QUIC ops (the pump-IPC-deadline B-half)~~: the brain-side deadline (REQ-HAZARD-PUMP-IPC-DEADLINE, KNOWN-HAZARDS 7.6) stops the single-threaded pump wedging when a peer black-holes, by bounding the pump's brain-IPC reads and escalating a TimedOut to a supervised restart. But the ROOT cause is broker-side: the broker's `net_open_stream` / `net_stream_send` / `net_dial` handlers m...\n 43|| **Durable in-daemon alarm scheduler (one-shot deadline machinery — the Q4/V3 deferral)**: the broker/brain restoration (ADR-0018 Q4) fixes the *rule* for one-shot scheduled events — persist the absolute `target-time` at creation; every brain start (update *or* crash) reads it and fires-if-due; **never reset on crash** (a user's \"remind me at 3pm\" is a commitment that must outlive any restart). But it deliberately does **not** build the machinery: the spt-core daemon has **no one-shot consumer today...\n 44|| **Autonomous session-digest freshness + Option-C re-home (M9 ADR-0008 amendment deferral)**: M9 re-founded the digest as an ON-DEMAND projection of normalized session logs (`spt-term::projection`; `spt-daemon::digest::project_endpoint_digest`) — snapshot-pull and the structured-delta-stream CONTRACT ship, deltas driven by pulls / `api digest-entry` pushes. Two pieces deferred, both because they have **no consumer yet** (Shell=M10, GUI/frontend pane, Gateway agent-window — none exist): (a) the **aut...\n 45|| **Two-origin `owl_message` context-tap subtype refinement (M10 ADR-0019 deferral)**: the two-origin merge's owl-message producer (`spt::api::startup` `deliver` closure) taps the live-agent **`emit` chokepoint**, so EVERY delivered frame — including resurfaced **notif** frames and shell-context rows — is recorded as a single coarse `owl_message` context-injection entry (`context_kind:\"owl_message\"`). The data is not wrong (a notif IS injected context), only coarsely labeled; the digest is a glanceab...\n*46|| **W2.5 — attached-presence + Kick-and-attach (M12 picker blue tri-state)**: M12 W2's picker ships online/offline status only; the **blue ■ \"attached\" tri-state** + the **\"Kick `<node>` and attach\"** confirm option were SCHEDULED (operator 2026-06-14, NOT dropped) as a dedicated slice built AFTER W2 lands, then wired back into the picker. Scope: (1) a **broker attach-presence query** — \"who is attached to endpoint E's PTY, by which node/surface\" — derive from the `presence.rs` PresenceLog conn c...\n 47|| **Cross-node shell TUNNEL relay (REQ-SHELL-4 cross-node-on-LAN carry)**: W3 built + proved the opaque byte tunnel **same-node only** (broker-homed `TunnelHub` loopback stream pair + the local control socket; `tunnel_e2e` round-trips `<EVENT`-looking bytes byte-exact). The **cross-node** path does NOT exist: `StreamFamily` (dispatch.rs) has no `Tunnel` variant, `classify_first_line` no tunnel arm, `SHELL_LINK` carries only relink/cmd/drive (no tunnel action), and every tunnel surface (`tunnel_ensure/sen...\n*48|| **Per-connection contiguous-sent cursor — structural guard against a decreasing-floor controller re-take (`REQ-HAZARD-CONTROLLER-WRITER-REORDER` follow-up)**: v0.13.0 P1c fixes the controller-writer reorder while KEEPING handoff's eager subscribe (fix #1 \"drop the subscribe\" was reverted — it's the standalone-resume mechanism), so the decreasing-floor double-take (handoff subscribe@K + serve_attach subscribe@0) is still PRESENT and made safe by: `controller_writer` epoch-gated under `send.lock()` (...\n 49|| **Adapter-manifest-enabled rc VT/mouse mode (`spt rc` scroll vs right-click-paste xor resolution)**: v0.13.0 P1/P1b's Windows `EnableMouseCapture` (for client-originated right-click bracketed paste, 7.18) makes Windows Terminal hand ALL wheel events to rc instead of scrolling its own scrollback; rc forwards the wheel to the harness as SGR reports (7.20) but ONLY when the harness has mouse-reporting on (`enabled && sgr`) — and **Claude Code's REPL never enables mouse tracking** (it has no internal scr...\n*50|| **Always-on endpoint kind + `#`-sigil addressing (ADR-0023; REQ-EP-8 / REQ-EP-9) — the core seam for adapter-bridged group/forum messaging**: a `[always-on]` manifest section declaring a resident, owner-less, addressable endpoint binary the daemon boot-launches + supervises **continuously** (independent of agent liveness, register-triggered, one per `<adapter>[:profile]`), which self-manages its `#`-addressed channel endpoints via the existing `api bind` and may call the already-ungated `endpoint wak...\n## DOCS-STRATEGY.md\n 10|\n*11|1. **Sub-10-minute killer quickstart** — runnable, deterministic, whole-working-thing-first, no placeholders. TTFHW (\"time to first hello world\") is the single most-cited conversion lever (~68% abandon trials over setup time).\n 12|2. **Diátaxis four-mode separation** — tutorial / how-to / reference / explanation, never mixed (mixing is the most-cited cause of confusing docs).\n 13|3. **Deterministic, real, copy-pasteable examples everywhere** — no `<YOUR_VALUE_HERE>` placeholders; real values that run. Serves humans *and* agents.\n 14|4. **Dual-depth agent exports** — `llms.txt` (slim curated index) + `llms-full.txt` (full concatenated export), auto-generated in CI, plus markdown content negotiation (`.md` suffix / `Accept: text/markdown`) which cuts agent token use ~90% vs HTML.\n...\n 86|## Capabilities      (one bullet per vertical, each → its own llms.txt)\n*87|- [Messaging](…/messaging/llms.txt): typed ops + file blobs; send / ring / ready.\n 88|- [Live-agent lifecycle](…/lifecycle/llms.txt): skeleton → live → dormant → suspended.\n 89|- … Terminal · Networking · Harness contract · Instances · Shells · Self-update\n 90|\n...\n 105|   Verify:                      spt --version      # spt 0.1.0 (proto v1)\n*106|## 2. The whole working thing   spt ready alice\n*107|                                spt ready bob\n 108|                                spt send bob --from alice \"hello\"\n 109|## 3. Run it                    spt poll --as bob\n 110|                                # → from=alice op=message body=\"hello\"\n...\n 146|\n*147|Top-level `--help` groups by audience (`AGENT COMMANDS` send/ring/ready/poll · `HARNESS COMMANDS` api … · `NODE COMMANDS` pair/subnet/update). Per-command: **USAGE · FLAGS · EXAMPLES (real values) · LEARN MORE**. Beyond gh: a **`spt <cmd> --help --json`** structured-help mode so dev-agents parse the contract without scraping, plus gh's uniform **`--json <fields>` / `--jq` / `--template`** trio on any list command. The `--json` help export is generated + CI-gated alongside rustdoc and the schema.\n## F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md\n 4|perri field seeds + spawn-fail taxonomy (2026-07-03, 16-part dispatch,\n*5|observed-vs-inferred flagged), doyle code recon of the spawn/ready seam\n 6|(anchors below, verified @b6d8357), perri design-review answers Q1–Q4\n 7|(2026-07-03; all four folded in — see §7).\n 8|\n...\n 23|its SessionStart hook, so `establish_perch` never runs (api/startup.rs:299 is\n*24|the ONLY writer of the `ready` marker), and the endpoint sits `ready=false`\n 25|**forever, silently** — while the operator was told it started.\n 26|\n*27|`ready=false` is already rendered by `spt list`/picker (roster.rs:69 →\n 28|cli.rs:2735/3826) but nothing gates or alarms on it: passive display only. The\n 29|attach path's `await_endpoint_session` (cli.rs:1854, 25 s) gates on broker\n 30|**session existence** — created synchronously at spawn, so it passes instantly\n...\n 41||---|------|----------|-------|\n*42|| 1 | TRUST-WEDGE — CC at workspace-trust dialog, null stdin; `ENDPOINT_RUN_STARTED` printed; ready=false forever | every fresh probe cwd, this session | SILENT |\n 43|| 2 | ACCOUNT-ROOT-MISMATCH — trust seeded in a DIFFERENT config root than the spawn resolves; wedges despite a valid-looking seed | Reavo/Brandon dual-root box (si1 vs b6b probes) | SILENT |\n 44|| 3 | PSYCHE-CRASHLOOP — nested psyche CC dies at trust dialog; wrapper respawned ~3/s, ordinal 5358 in 30 min | F-h; now guarded by adapter v0.13.0 give-up + core C3(b) | SILENT (guarded) |\n 45|| 4 | ADAPTER-UNRESOLVED — no registered adapter hosts the parent binary | seed→listen path, bash parent | LOUD pre-spawn (good shape) |\n...\n 67|The silent class (modes 1, 2, 3, 6) must fail with the same loudness as the\n*68|pre-spawn class (modes 4, 5) already does.\n 69|\n 70|### Half A — bind-deadline honesty (spt-core, harness-agnostic)\n 71|\n 72|After the spawn RPC returns in `cmd_endpoint_run --start`, the CLI **awaits\n*73|bind** — the appearance of the `ready` marker (`resolve_ready_file`, the\n 74|`establish_perch` effect) — bounded by a deadline:\n 75|\n 76|- **Success:** `ENDPOINT_RUN_STARTED:{id}` moves to AFTER the bind is observed\n...\n 82|  picker render the honest state. A later successful bind clears the stamp\n*83|  (`establish_perch` already rewrites state; clearing rides there).\n 84|- Classification probes (all CLI-observable; no daemon change needed to\n*85|  detect): child pid alive? broker `has_session` still true? ready marker\n 86|  present? →\n 87|  - `WEDGED_PRE_BIND` — pid alive, session alive, never bound (modes 1, 2)\n 88|  - `EXITED_PRE_BIND` — child exited before binding (mode 5-shaped recipes\n...\n 90|  - `NO_SESSION` — broker session vanished mid-wait (mode 6 shape)\n*91|- Deadline: **default 60 s** (field-confirmed: real binds land t+5–15 s;\n 92|  every observed bringup exceeding ~20 s was a wedge that never bound — the\n*93|  deadline separates cleanly at ≥30 s; **never configure below 30 s**),\n 94|  overridable per-adapter via a manifest key (the bringup-time contract\n 95|  belongs to the adapter) and per-invocation via `--bind-deadline <secs>`\n 96|  (the per-node escape for cold-broker first-spawn / loaded boxes). Escape\n...\n 131|   via `resolve_program_in_dir` like every other adapter-manifest program.\n*132|3. **Bounded + loud:** runtime capped (30 s); non-zero exit or timeout ABORTS\n 133|   the session spawn with `ENDPOINT_SPAWN_FAIL:{id}: reason=PRE_SPAWN_FAIL:\n 134|   <exit/detail>` on CLI stderr (relayed through the spawn RPC error — this\n*135|   path already reaches the CLI as `ENDPOINT_RUN_FAIL` does today).\n 136|4. **Idempotency contract:** the command must be safe to run on every spawn\n 137|   (documented in MANIFEST.md; the seeder re-asserting an existing trust entry\n 138|   is a no-op).\n...\n 163|  owlery cwd is trust-seeded by the same pre-spawn seam, and the crashloop\n*164|  backstop (C3(b) + adapter give-up) already guards the symptom.\n 165|- No mode-6 work: FIXED on 0.23.0 by F-028 B1/B4 (perri definitive repro).\n 166|  The Half-A deadline token still catches its shape if it ever regresses.\n 167|\n...\n 174|| 3 PSYCHE-CRASHLOOP | Half B (owlery cwd seed) | existing C3(b)/v0.13.0 guard |\n*175|| 4 ADAPTER-UNRESOLVED | — (already good) | existing pre-spawn error |\n*176|| 5 STALE-CLI-CONTRACT | — (already good) | existing / Half A `EXITED_PRE_BIND` |\n 177|| 6 RELAUNCH-NEVER-BINDS | FIXED (F-028 B1/B4, 0.23.0-verified) | Half A `NO_SESSION`/`WEDGED_PRE_BIND` (regression net only) |\n 178|\n 179|## 5. Requirement seeds (register at wave start, `required_stages=[]` until active)\n...\n 206|\n*207|1. **Deadline default — ANSWERED:** 60 s safe with margin (real binds t+5–15 s;\n*208|   every >20 s bringup was a wedge, never a slow success). Floor 30 s; per-node\n 209|   override kept for cold-broker first-spawn / loaded boxes. Folded into §3.\n 210|2. **`pre_spawn` on resume — ANSWERED: YES.** Resume re-checks trust (INT-2:\n 211|   resumed CC died at the trust dialog). Folded into §3 Half B.\n## F-030-PSYCHE-EPHEMERAL-DESIGN.md\n 2|\n*3|Status: **READY FOR BUILD 2026-07-04 — wave called by operator.** Author: doyle.\n 4|Inputs: perri `PSYCHE-EPHEMERAL-PLAN.md` (adapter half, doyle-gated PASS; built on\n 5|parked branch `psyche-ephemeral-parked`, publish held for this core wave), the\n 6|2026-07-04 field case (both directions — v0.13.2 bad-ship + hall-bf multi-subnet\n...\n 25|  ~5 s reconcile tick when it isn't resident.\n*26|- Meanwhile the **per-event driver stack already exists, tested, with ZERO\n 27|  production callers**: `resume_psyche` (spt-live resume.rs:175, mode-validated\n 28|  spawn + nested rebind), `run_psyche_turn` (spt-live turn.rs:72, bounded\n 29|  stdin→stdout turn; its only production caller is the ADR-0013 conflict-merge\n...\n 36|**(a) The adapter cannot HOLD residency.** The resident runner's per-pulse\n*37|liveness poll (`spt ready <id> --once`) hard-refuses on a multi-subnet home\n*38|(`READY_FAIL: … pass --subnet`). The nested psyche process **cannot know its\n 39|subnet/home**: core fills only `base_keys` = `{id}`, `{session_id}`, `{node}`\n 40|(lifecycle.rs:338) plus role-local extras — no `{subnet}`, no `{home}`. On\n 41|hfenduleam (2 subnets since BIGNET joined 7/3) the runner died instantly,\n...\n 48|exit after the seed turn tripped the residency machinery, which tore down the\n*49|endpoint's hosted state — ready marker removed, never re-stamped, every\n 50|force-native gated `leg=cli-gate-not-hosted` **permanently** (field brick,\n 51|operator-hit on hall-bf, contained same-day by v0.13.3).\n 52|\n...\n 61|  image only for a bounded turn — the own-copy machinery loses its reason.\n*62|- Psyche TURNS already work daemon-driven today (`psyche_download`, revival\n 63|  prompt, reconcile merge) — proof the per-event shape is the natural one.\n 64|\n 65|## 2. Field evidence (2026-07-04 — provenance)\n...\n 70|| 2 | hall-bf-psyche boot-churn ~5 s cadence, ordinal 6491+, synthetic `sess-<pid>-<ts>` ids, no real session ever binds; starts exactly at BIGNET join (7/3) | operator box, real home | adapter can't hold residency |\n*71|| 3 | Poll-verb root repro: `spt ready <id> --once` on 2-subnet home → exit 1, `READY_FAIL … pass --subnet`, empty stdout | perri, clean repro | adapter can't hold residency |\n*72|| 4 | v0.13.2 shim-exit → residency teardown → ready removed → permanent `leg=cli-gate-not-hosted` | hall-bf + perri wake-confirm | residency expectation destructive |\n*73|| 5 | Psyche turns land fine via daemon-driven paths while the resident churns | hall-bf digest (`psyche_download` at every boundary) | per-event shape already proven |\n 74|| 6 | ~12/min re-host cadence NEVER tripped the C3(b) thrash guard (boot records are apparently not ledger boundaries to the guard) | hall-bf | guard blindness (rides this wave) |\n 75|\n 76|## 3. Design\n...\n 101|budget/cooldown shape): N consecutive turn failures → stamp\n*102|`psyche_host_error` (kept name; it is already the harness-reachable pattern) +\n 103|cooldown, reset on a successful turn. No respawn storm exists anymore because\n 104|there is nothing resident to respawn.\n 105|\n...\n 115|- `resume_psyche(ResumeMode::…)` validates the custody key before spawn\n*116|  (already implemented — resume.rs:183).\n 117|- **Reseed path:** when the psyche's own session is lost/invalid (first host,\n 118|  store wipe, harness-side expiry), fall back to\n 119|  a fresh-with-preload turn — `download_psyche_context` composes\n...\n 136|- **The teardown that made direction (b) destructive is deleted**: no psyche\n*137|  state may ever remove the PARENT endpoint's ready marker or hosted state.\n 138|  Psyche trouble stamps psyche fields only. (This is the conformance test's\n 139|  core assertion.)\n 140|- C3(b) guard rework rides here (evidence #6): the failure budget counts\n...\n 149|**nested `{parent}/{nested}` ids resolve under the parent's home** —\n*150|subnet-less resolution for nested perches (`resolve_perch_path`/ready-verb\n 151|family). Additionally expose a single `{subnet}` base_keys fill **when\n 152|known** (lifecycle.rs:338; absent → LOUD missing-key fail, the `{node}`\n 153|precedent on :344). **There is deliberately NO `{home}` key** (doyle W4 Q1: the\n*154|endpoint home subnet is one concept, and `home:` is already the subnet-name\n 155|qualifier position — a `{home}` template key would only invite meaning-drift).\n 156|Red-first = evidence #3's exact repro: 2-subnet home, nested-id verb, must\n 157|succeed.\n...\n 195|  rig, not separately fixed — the ephemeral model should erase it; if a\n*196|  residue survives, it becomes its own seed with the rig already in hand.\n 197|\n 198|## 4. Coverage matrix\n 199|\n...\n 225|  0.13.2): psyche failure of ANY shape must not remove/alter the parent\n*226|  endpoint's ready/hosted state, and hosting must not churn-respawn.\n 227|  Conformance int = the hall-bf shape: multi-subnet home, live endpoint,\n 228|  failing psyche → parent stays deliverable, no rehost churn, error stamped.\n 229|- `REQ-HAZARD-THRASH-GUARD-BLIND` — hazard (paid-for, evidence #6): the\n...\n 254|   timeouts; the pulse-fired turn needs a config default — propose the\n*255|   existing pulse-period-derived bound, floor 30 s).\n 256|2. Does the checkpoint/commune choreography want a custody-transition turn at\n 257|   parent boundary (psyche notices the parent reset), or stay purely\n 258|   event-driven? (Design assumes purely event-driven; cheap to add later.)\n 259|3. `{subnet}` fill naming — RESOLVED (doyle W4 Q1): a single `{subnet}` key,\n*260|   no `{home}` key (the endpoint home subnet is one concept; `home:` is already\n 261|   the subnet-name qualifier position). Confirmed against CONTEXT.md glossary.\n## F-035-CONFLICT.md\n 3|**Raised by:** todlando, mid-build, 2026-07-09. **Build HELD** (WIP uncommitted on\n*4|`f-035-window-drain-honor`). doyle's COLLAPSE ruling collides with a **shipped**\n 5|acceptance gate my RCA (docs/F-035-RCA.md) missed. Need an A/B/C ruling before I proceed.\n 6|\n 7|---\n...\n 13|\n*14|**`crates/spt/tests/idle_edge_drain_e2e.rs:89`** — `fn spool_while_active_then_idle_fires_injection`\n*15|`// [int->REQ-MSG-IDLE-EDGE-DRAIN] [int->REQ-HAZARD-DELIVERY-STARVATION]`\n 16|\n 17|- **Setup:** spools `spool_message_deferred_at(perch, \"peer\", …)` — which is\n 18|  `insert_message(…, WINDOW_ACTIVE_ONLY, …)` (spool.rs:363). So the row is\n...\n 26|\n*27|`REQ-MSG-IDLE-EDGE-DRAIN` (F-023 leg 2) and `REQ-SEND-WINDOW-DRAIN-HONOR` (F-035) are\n 28|directly incompatible for the relay-less spt-hosted class. The active/idle asymmetry I\n 29|called a bug in the RCA is **intentional** for that class.\n 30|\n...\n 35|For an **spt-hosted** endpoint (broker owns the PTY, `controllable`, relay-less), the\n*36|idle-edge/parked drain is the **ONLY** carrier for an `active_only` row. Traced every path:\n 37|\n 38|| carrier | `active_only` to spt-hosted? |\n 39||---|---|\n...\n 41|| Send-time idle re-offer (`cli.rs:5734`) | **NO** — gated `&& !active_only`. |\n*42|| Hook poll `drain_active_window` (`api poll`) | **N/A** — that is the HARNESS-hosted hook channel; an spt-hosted endpoint has no `api poll` harness. |\n*43|| **Idle-edge / parked drain `drain_idle_spool`** | **YES** — the F-023 leg-2 behavior (`include_deferred = !resting`). **This is the only carrier.** |\n 44|\n 45|So: `active_only` is designed as *\"the active **hook** window\"* — meant for\n 46|**harness-hosted** endpoints that `api poll`. An **spt-hosted** endpoint has no hook, so\n...\n 59|  `active_only`+spt-hosted is knowingly revoked (that class starves, or gets an\n*60|  evaporate/refuse-at-send policy instead). Requires re-scoping `REQ-MSG-IDLE-EDGE-DRAIN`\n 61|  + rewriting its int.\n 62|- **(B) never-starve wins** — the idle edge *should* carry `active_only` to spt-hosted\n 63|  (F-023 correct). Then **F-035 is mis-scoped**: *\"never wakes\"* is the **harness** hook\n...\n 65|  never a contract for the hook-less spt-hosted class. **RETIRE or NARROW\n*66|  REQ-SEND-WINDOW-DRAIN-HONOR; no code change.**\n 67|- **(C) class split** — spt-hosted delivers `active_only` at idle **without waking**\n 68|  (non-interrupting background inject) vs harness-hosted holds-for-poll. New behavior,\n 69|  larger; needs a \"deliver-but-don't-wake\" inject mode the translation binary honors.\n...\n 71|**todlando lean:** the carrier table points at **(B)** — the field-doc *\"never wakes an\n*72|idle target\"* (cli.rs:85) describes the harness hook model, and F-023 already reasoned\n 73|through the spt-hosted no-hook gap deliberately. The original F-035 *symptom* (a\n 74|`--deferred` background row reaching an idle spt-hosted agent) may be **working as\n 75|designed** — it is the only way that class can ever be delivered. If the true concern is\n*76|*\"it interrupts,\"* that is **(C)**, not the drain-exclusion of **(A)**. But this is your call.\n 77|\n 78|My WIP (spool.rs window-axis collapse + inject.rs + 2 units) is staged **uncommitted** — I\n 79|touched neither delivery.rs nor worker.rs. Standing by for A / B / C.\n## F-035-RCA.md\n*1|<!-- [doc->REQ-SEND-WINDOW-DRAIN-HONOR] F-035 RCA — report-before-fix. -->\n*2|# F-035 RCA — idle-edge parked-drain ignores the delivery-window tag\n 3|\n 4|**Status:** RCA-first, **report before fixing** (doyle-dispatched 2026-07-09).\n*5|**REQ:** `REQ-SEND-WINDOW-DRAIN-HONOR` (registered, `required_stages = []` — activate on doyle's fix ruling).\n 6|**Investigator:** todlando. **Off:** `origin/main@2c05dc9` (v0.30.2).\n 7|\n 8|---\n...\n 20|\n*21|For an **spt-hosted, relay-less** endpoint (the class the idle-edge drain targets),\n 22|such a row is delivered through the translation binary at the idle window — the\n 23|exact delivery the tag forbids.\n 24|\n...\n 42|\n*43|### ACTIVE drain — honors the axis (correct)\n 44|\n*45|`spool::drain_active_window_inner`:\n 46|\n 47|```\n 48|include_deferred ? \"WHERE delivered=0 AND \\\"window\\\" != 'idle_only'\"\n...\n 53|","truncated":true,"truncatedBy":"bytes","totalLines":767,"totalBytes":89908,"outputLines":493,"outputBytes":51154,"lastLinePartial":false,"firstLineExceedsLimit":false},"linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}},"truncation":{"direction":"head","truncatedBy":"bytes","totalLines":767,"totalBytes":89908,"outputLines":493,"outputBytes":51154,"shownRange":{"start":1,"end":493},"nextOffset":494}}},"isError":false,"timestamp":1784247411716}}
{"type":"message","id":"331a1549","parentId":"5005ac8e","timestamp":"2026-07-17T00:16:51.725Z","message":{"role":"toolResult","toolCallId":"call_L4n1lExWX59Eih3xxGdfltrP|fc_099a7567dadef391016a5974738bb8819b9a1abc5ae98b4c47","toolName":"read","content":[{"type":"text","text":"//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n//! a dispatcher GENERATION whose death drops its workers' broker conns\n//! exactly like a production brain death). NO manual re-serve anywhere: the\n//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n//! hand-stepped a known stream and bypassed exactly the rediscovery/\n//! classification seam that was broken in the field.\n//!\n//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\n//!   endpoint/origin; restart the dispatcher generation only → the\n//!   historical stream neither takes nor clears the current controller\n//!   (post-restart input still renders; no EOF; no replay-storm log line;\n//!   the retired row left the broker's enumeration).\n//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n//!   restart the dispatcher → durable classification (the pinned opener)\n//!   resumes the SAME operator stream.\n//! - D3: ONE injected transient worker-start failure → the claim requeues\n//!   and the retry serves the stream (no permanent abandonment, no\n//!   duplicate controller).\n//!\n//! HEAVY AT BIRTH (FLAKE-LEDGER #15): spawns real child processes + two\n//! hermetic QUIC brokers per test.\n\nuse std::process::{Child, Command, Stdio};\nuse std::sync::atomic::{AtomicU32, Ordering};\nuse std::sync::{Arc, OnceLock};\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nuse spt_daemon::attach::{request_attach, send_attach_input};\nuse spt_daemon::brain::{Brain, BrokerEvent};\nuse spt_daemon::effect::{Minter, MintedOp};\nuse spt_daemon::msg::SpawnReq;\nuse spt_daemon::nethost::{NetConfig, NetHost};\nuse spt_daemon::Broker;\nuse spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\nuse spt_proto::identity::Identity;\nuse spt_term::ScreenGrid;\n\n/// Hold the temp home for the whole test binary (the attach.rs discipline).\nstatic HOME: OnceLock<tempfile::TempDir> = OnceLock::new();\n\nfn init_home() {\n    HOME.get_or_init(|| {\n        let dir = tempfile::TempDir::new().expect(\"temp dir\");\n        std::env::set_var(\"SPT_HOME\", dir.path());\n        dir\n    });\n}\n\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\nfn unique_name() -> String {\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\n    format!(\"spt-daemon-rdsp-{}-{}.sock\", std::process::id(), n)\n}\n\nfn hermetic() -> NetConfig {\n    NetConfig {\n        identity: Identity::generate(),\n        relay: RelayPolicy::Disabled,\n        discovery: LocalDiscovery::Off,\n        scope: BindScope::Loopback,\n        membership: None,\n    }\n}\n\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\n    let broker =\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\n    let serve = Arc::clone(&broker);\n    thread::spawn(move || {\n        let _ = serve.serve();\n    });\n    broker\n}\n\nfn connect_retry(name: &str) -> Brain {\n    for _ in 0..300 {\n        if let Ok(b) = Brain::cold_start(name, 1) {\n            return b;\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    panic!(\"brain could not connect\");\n}\n\nfn echo_spawn_req(endpoint: &str) -> SpawnReq {\n    #[cfg(unix)]\n    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n    #[cfg(windows)]\n    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\n    SpawnReq {\n        program,\n        args,\n        rows: 24,\n        cols: 80,\n        endpoint: endpoint.to_string(),\n        cwd: None,\n        env: Default::default(),\n        translation_binary: None,\n        adapter: String::new(),\n        install_dir: None,\n    }\n}\n\n/// One REAL dispatcher generation: the `dispatch_fixture` child running\n/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\nstruct Generation {\n    child: Child,\n    log: std::path::PathBuf,\n}\n\nimpl Generation {\n    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n        cmd.arg(broker_name)\n            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\n            .stdout(Stdio::null())\n            .stderr(Stdio::from(f));\n        for (k, v) in extra_env {\n            cmd.env(k, v);\n        }\n        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n        Generation { child, log }\n    }\n\n    fn stderr_log(&self) -> String {\n        std::fs::read_to_string(&self.log).unwrap_or_default()\n    }\n\n    fn kill(mut self) {\n        let _ = self.child.kill();\n        let _ = self.child.wait();\n    }\n}\n\n/// The A-side (target broker) peer-stream ids, via a probe brain.\nfn peer_stream_ids(probe: &mut Brain) -> Vec<u64> {\n    probe\n        .net_streams()\n        .expect(\"net-streams\")\n        .streams\n        .iter()\n        .filter(|s| !s.initiated_locally)\n        .map(|s| s.stream_id)\n        .collect()\n}\n\n/// Render the operator's viewport (dedup `Output` by seq — replays after a\n/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n/// Returns `true` if an EOF for the stream arrived while rendering (the\n/// steal-clears-controller symptom D1 must NOT see).\nfn render_until(\n    operator: &mut Brain,\n    stream_id: u64,\n    decoder: &mut AttachDecoder,\n    cursor: &mut u64,\n    buf: &mut Vec<u8>,\n    needle: &[u8],\n    deadline: Duration,\n) -> bool {\n    let contains = |hay: &[u8], n: &[u8]| hay.windows(n.len()).any(|w| w == n);\n    let end = Instant::now() + deadline;\n    let mut saw_eof = false;\n    while !contains(buf, needle) {\n        assert!(\n            Instant::now() < end,\n            \"needle {:?} never rendered; viewport so far: {:?}; eof={saw_eof}\",\n            String::from_utf8_lossy(needle),\n            String::from_utf8_lossy(buf)\n        );\n        match operator.read_event().expect(\"operator read event\") {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    if let AttachRecord::Output { seq, data_b64 } = rec {\n                        if seq < *cursor {\n                            continue; // replay duplicate — dedup at the render cursor\n                        }\n                        *cursor = seq + 1;\n                        buf.extend_from_slice(\n                            &spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),\n                        );\n                    }\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                saw_eof = true;\n            }\n            _ => {}\n        }\n    }\n    saw_eof\n}\n\n[Showing lines 1-203 of 621. Use :204 to continue]"}],"details":{"truncation":{"content":"//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\r\n//! KNOWN-HAZARDS 7.41): every leg drives the REAL\r\n//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\r\n//! a dispatcher GENERATION whose death drops its workers' broker conns\r\n//! exactly like a production brain death). NO manual re-serve anywhere: the\r\n//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\r\n//! hand-stepped a known stream and bypassed exactly the rediscovery/\r\n//! classification seam that was broken in the field.\r\n//!\r\n//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\r\n//!   endpoint/origin; restart the dispatcher generation only → the\r\n//!   historical stream neither takes nor clears the current controller\r\n//!   (post-restart input still renders; no EOF; no replay-storm log line;\r\n//!   the retired row left the broker's enumeration).\r\n//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\r\n//!   restart the dispatcher → durable classification (the pinned opener)\r\n//!   resumes the SAME operator stream.\r\n//! - D3: ONE injected transient worker-start failure → the claim requeues\r\n//!   and the retry serves the stream (no permanent abandonment, no\r\n//!   duplicate controller).\r\n//!\r\n//! HEAVY AT BIRTH (FLAKE-LEDGER #15): spawns real child processes + two\r\n//! hermetic QUIC brokers per test.\r\n\r\nuse std::process::{Child, Command, Stdio};\r\nuse std::sync::atomic::{AtomicU32, Ordering};\r\nuse std::sync::{Arc, OnceLock};\r\nuse std::thread;\r\nuse std::time::{Duration, Instant};\r\n\r\nuse spt_daemon::attach::{request_attach, send_attach_input};\r\nuse spt_daemon::brain::{Brain, BrokerEvent};\r\nuse spt_daemon::effect::{Minter, MintedOp};\r\nuse spt_daemon::msg::SpawnReq;\r\nuse spt_daemon::nethost::{NetConfig, NetHost};\r\nuse spt_daemon::Broker;\r\nuse spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\r\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\r\nuse spt_proto::identity::Identity;\r\nuse spt_term::ScreenGrid;\r\n\r\n/// Hold the temp home for the whole test binary (the attach.rs discipline).\r\nstatic HOME: OnceLock<tempfile::TempDir> = OnceLock::new();\r\n\r\nfn init_home() {\r\n    HOME.get_or_init(|| {\r\n        let dir = tempfile::TempDir::new().expect(\"temp dir\");\r\n        std::env::set_var(\"SPT_HOME\", dir.path());\r\n        dir\r\n    });\r\n}\r\n\r\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\r\nfn unique_name() -> String {\r\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\r\n    format!(\"spt-daemon-rdsp-{}-{}.sock\", std::process::id(), n)\r\n}\r\n\r\nfn hermetic() -> NetConfig {\r\n    NetConfig {\r\n        identity: Identity::generate(),\r\n        relay: RelayPolicy::Disabled,\r\n        discovery: LocalDiscovery::Off,\r\n        scope: BindScope::Loopback,\r\n        membership: None,\r\n    }\r\n}\r\n\r\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\r\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\r\n    let broker =\r\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\r\n    let serve = Arc::clone(&broker);\r\n    thread::spawn(move || {\r\n        let _ = serve.serve();\r\n    });\r\n    broker\r\n}\r\n\r\nfn connect_retry(name: &str) -> Brain {\r\n    for _ in 0..300 {\r\n        if let Ok(b) = Brain::cold_start(name, 1) {\r\n            return b;\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    panic!(\"brain could not connect\");\r\n}\r\n\r\nfn echo_spawn_req(endpoint: &str) -> SpawnReq {\r\n    #[cfg(unix)]\r\n    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\r\n    #[cfg(windows)]\r\n    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\r\n    SpawnReq {\r\n        program,\r\n        args,\r\n        rows: 24,\r\n        cols: 80,\r\n        endpoint: endpoint.to_string(),\r\n        cwd: None,\r\n        env: Default::default(),\r\n        translation_binary: None,\r\n        adapter: String::new(),\r\n        install_dir: None,\r\n    }\r\n}\r\n\r\n/// One REAL dispatcher generation: the `dispatch_fixture` child running\r\n/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\r\n/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\r\nstruct Generation {\r\n    child: Child,\r\n    log: std::path::PathBuf,\r\n}\r\n\r\nimpl Generation {\r\n    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\r\n        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\r\n        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\r\n        cmd.arg(broker_name)\r\n            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\r\n            .stdout(Stdio::null())\r\n            .stderr(Stdio::from(f));\r\n        for (k, v) in extra_env {\r\n            cmd.env(k, v);\r\n        }\r\n        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\r\n        Generation { child, log }\r\n    }\r\n\r\n    fn stderr_log(&self) -> String {\r\n        std::fs::read_to_string(&self.log).unwrap_or_default()\r\n    }\r\n\r\n    fn kill(mut self) {\r\n        let _ = self.child.kill();\r\n        let _ = self.child.wait();\r\n    }\r\n}\r\n\r\n/// The A-side (target broker) peer-stream ids, via a probe brain.\r\nfn peer_stream_ids(probe: &mut Brain) -> Vec<u64> {\r\n    probe\r\n        .net_streams()\r\n        .expect(\"net-streams\")\r\n        .streams\r\n        .iter()\r\n        .filter(|s| !s.initiated_locally)\r\n        .map(|s| s.stream_id)\r\n        .collect()\r\n}\r\n\r\n/// Render the operator's viewport (dedup `Output` by seq — replays after a\r\n/// generation restart re-transmit and are dropped at this cursor; a re-serve's\r\n/// synthesized repaint may jump FORWARD, which is accepted and baselined)\r\n/// until `needle` appears in the accumulated bytes, or panic at the deadline.\r\n/// Returns `true` if an EOF for the stream arrived while rendering (the\r\n/// steal-clears-controller symptom D1 must NOT see).\r\nfn render_until(\r\n    operator: &mut Brain,\r\n    stream_id: u64,\r\n    decoder: &mut AttachDecoder,\r\n    cursor: &mut u64,\r\n    buf: &mut Vec<u8>,\r\n    needle: &[u8],\r\n    deadline: Duration,\r\n) -> bool {\r\n    let contains = |hay: &[u8], n: &[u8]| hay.windows(n.len()).any(|w| w == n);\r\n    let end = Instant::now() + deadline;\r\n    let mut saw_eof = false;\r\n    while !contains(buf, needle) {\r\n        assert!(\r\n            Instant::now() < end,\r\n            \"needle {:?} never rendered; viewport so far: {:?}; eof={saw_eof}\",\r\n            String::from_utf8_lossy(needle),\r\n            String::from_utf8_lossy(buf)\r\n        );\r\n        match operator.read_event().expect(\"operator read event\") {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    if let AttachRecord::Output { seq, data_b64 } = rec {\r\n                        if seq < *cursor {\r\n                            continue; // replay duplicate — dedup at the render cursor\r\n                        }\r\n                        *cursor = seq + 1;\r\n                        buf.extend_from_slice(\r\n                            &spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),\r\n                        );\r\n                    }\r\n                }\r\n            }\r\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\r\n                saw_eof = true;\r\n            }\r\n            _ => {}\r\n        }\r\n    }\r\n    saw_eof\r","truncated":true,"truncatedBy":"lines","totalLines":621,"totalBytes":7475,"outputLines":203,"outputBytes":7475,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\r\n//! KNOWN-HAZARDS 7.41): every leg drives the REAL\r\n//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\r\n//! a dispatcher GENERATION whose death drops its workers' broker conns\r\n//! exactly like a production brain death). NO manual re-serve anywhere: the\r\n//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\r\n//! hand-stepped a known stream and bypassed exactly the rediscovery/\r\n//! classification seam that was broken in the field.\r\n//!\r\n//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\r\n//!   endpoint/origin; restart the dispatcher generation only → the\r\n//!   historical stream neither takes nor clears the current controller\r\n//!   (post-restart input still renders; no EOF; no replay-storm log line;\r\n//!   the retired row left the broker's enumeration).\r\n//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\r\n//!   restart the dispatcher → durable classification (the pinned opener)\r\n//!   resumes the SAME operator stream.\r\n//! - D3: ONE injected transient worker-start failure → the claim requeues\r\n//!   and the retry serves the stream (no permanent abandonment, no\r\n//!   duplicate controller).\r\n//!\r\n//! HEAVY AT BIRTH (FLAKE-LEDGER #15): spawns real child processes + two\r\n//! hermetic QUIC brokers per test.\r\n\r\nuse std::process::{Child, Command, Stdio};\r\nuse std::sync::atomic::{AtomicU32, Ordering};\r\nuse std::sync::{Arc, OnceLock};\r\nuse std::thread;\r\nuse std::time::{Duration, Instant};\r\n\r\nuse spt_daemon::attach::{request_attach, send_attach_input};\r\nuse spt_daemon::brain::{Brain, BrokerEvent};\r\nuse spt_daemon::effect::{Minter, MintedOp};\r\nuse spt_daemon::msg::SpawnReq;\r\nuse spt_daemon::nethost::{NetConfig, NetHost};\r\nuse spt_daemon::Broker;\r\nuse spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};\r\nuse spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\r\nuse spt_proto::identity::Identity;\r\nuse spt_term::ScreenGrid;\r\n\r\n/// Hold the temp home for the whole test binary (the attach.rs discipline).\r\nstatic HOME: OnceLock<tempfile::TempDir> = OnceLock::new();\r\n\r\nfn init_home() {\r\n    HOME.get_or_init(|| {\r\n        let dir = tempfile::TempDir::new().expect(\"temp dir\");\r\n        std::env::set_var(\"SPT_HOME\", dir.path());\r\n        dir\r\n    });\r\n}\r\n\r\nstatic SEQ: AtomicU32 = AtomicU32::new(0);\r\nfn unique_name() -> String {\r\n    let n = SEQ.fetch_add(1, Ordering::Relaxed);\r\n    format!(\"spt-daemon-rdsp-{}-{}.sock\", std::process::id(), n)\r\n}\r\n\r\nfn hermetic() -> NetConfig {\r\n    NetConfig {\r\n        identity: Identity::generate(),\r\n        relay: RelayPolicy::Disabled,\r\n        discovery: LocalDiscovery::Off,\r\n        scope: BindScope::Loopback,\r\n        membership: None,\r\n    }\r\n}\r\n\r\nfn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {\r\n    let host = NetHost::start(hermetic()).expect(\"net host start\");\r\n    let broker =\r\n        Broker::bind_in_with_net(name, dir.join(\"effects.log\"), Some(host)).expect(\"bind broker\");\r\n    let serve = Arc::clone(&broker);\r\n    thread::spawn(move || {\r\n        let _ = serve.serve();\r\n    });\r\n    broker\r\n}\r\n\r\nfn connect_retry(name: &str) -> Brain {\r\n    for _ in 0..300 {\r\n        if let Ok(b) = Brain::cold_start(name, 1) {\r\n            return b;\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    panic!(\"brain could not connect\");\r\n}\r\n\r\nfn echo_spawn_req(endpoint: &str) -> SpawnReq {\r\n    #[cfg(unix)]\r\n    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\r\n    #[cfg(windows)]\r\n    let (program, args) = (\"findstr\".to_string(), vec![\".\".to_string()]);\r\n    SpawnReq {\r\n        program,\r\n        args,\r\n        rows: 24,\r\n        cols: 80,\r\n        endpoint: endpoint.to_string(),\r\n        cwd: None,\r\n        env: Default::default(),\r\n        translation_binary: None,\r\n        adapter: String::new(),\r\n        install_dir: None,\r\n    }\r\n}\r\n\r\n/// One REAL dispatcher generation: the `dispatch_fixture` child running\r\n/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\r\n/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\r\nstruct Generation {\r\n    child: Child,\r\n    log: std::path::PathBuf,\r\n}\r\n\r\nimpl Generation {\r\n    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\r\n        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\r\n        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\r\n        cmd.arg(broker_name)\r\n            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\r\n            .stdout(Stdio::null())\r\n            .stderr(Stdio::from(f));\r\n        for (k, v) in extra_env {\r\n            cmd.env(k, v);\r\n        }\r\n        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\r\n        Generation { child, log }\r\n    }\r\n\r\n    fn stderr_log(&self) -> String {\r\n        std::fs::read_to_string(&self.log).unwrap_or_default()\r\n    }\r\n\r\n    fn kill(mut self) {\r\n        let _ = self.child.kill();\r\n        let _ = self.child.wait();\r\n    }\r\n}\r\n\r\n/// The A-side (target broker) peer-stream ids, via a probe brain.\r\nfn peer_stream_ids(probe: &mut Brain) -> Vec<u64> {\r\n    probe\r\n        .net_streams()\r\n        .expect(\"net-streams\")\r\n        .streams\r\n        .iter()\r\n        .filter(|s| !s.initiated_locally)\r\n        .map(|s| s.stream_id)\r\n        .collect()\r\n}\r\n\r\n/// Render the operator's viewport (dedup `Output` by seq — replays after a\r\n/// generation restart re-transmit and are dropped at this cursor; a re-serve's\r\n/// synthesized repaint may jump FORWARD, which is accepted and baselined)\r\n/// until `needle` appears in the accumulated bytes, or panic at the deadline.\r\n/// Returns `true` if an EOF for the stream arrived while rendering (the\r\n/// steal-clears-controller symptom D1 must NOT see).\r\nfn render_until(\r\n    operator: &mut Brain,\r\n    stream_id: u64,\r\n    decoder: &mut AttachDecoder,\r\n    cursor: &mut u64,\r\n    buf: &mut Vec<u8>,\r\n    needle: &[u8],\r\n    deadline: Duration,\r\n) -> bool {\r\n    let contains = |hay: &[u8], n: &[u8]| hay.windows(n.len()).any(|w| w == n);\r\n    let end = Instant::now() + deadline;\r\n    let mut saw_eof = false;\r\n    while !contains(buf, needle) {\r\n        assert!(\r\n            Instant::now() < end,\r\n            \"needle {:?} never rendered; viewport so far: {:?}; eof={saw_eof}\",\r\n            String::from_utf8_lossy(needle),\r\n            String::from_utf8_lossy(buf)\r\n        );\r\n        match operator.read_event().expect(\"operator read event\") {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    if let AttachRecord::Output { seq, data_b64 } = rec {\r\n                        if seq < *cursor {\r\n                            continue; // replay duplicate — dedup at the render cursor\r\n                        }\r\n                        *cursor = seq + 1;\r\n                        buf.extend_from_slice(\r\n                            &spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),\r\n                        );\r\n                    }\r\n                }\r\n            }\r\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\r\n                saw_eof = true;\r\n            }\r\n            _ => {}\r\n        }\r\n    }\r\n    saw_eof\r\n}","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\redispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":621,"totalBytes":7475,"outputLines":203,"outputBytes":7475,"shownRange":{"start":1,"end":203},"nextOffset":204}}},"isError":false,"timestamp":1784247411725}}
{"type":"message","id":"2b8e2d95","parentId":"331a1549","timestamp":"2026-07-17T00:16:51.738Z","message":{"role":"toolResult","toolCallId":"call_UJt6vJp4AJxM3tVfzomqqUh8|fc_099a7567dadef391016a5974738bc0819b8fb83efc65fb323d","toolName":"read","content":[{"type":"text","text":"    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n…\n        loop {\n            match self.read_event()? {\n                BrokerEvent::NetStreams(ev) => return Ok(ev),\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A).\n    /// The dispatcher calls this when a serve loop runs to completion, so no\n    /// future dispatcher generation rediscovers and replays the stream\n    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\n    /// answers `error` (KH-2.3 N-1 window — retirement then waits for the\n    /// broker to cycle onto the new binary).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\n        self.send(\n            KIND_NET_STREAM_RETIRE,\n            serde_json::to_value(NetStreamRetireReq { stream_id })\n                .expect(\"NetStreamRetireReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_RETIRED => {\n                    let reply: NetStreamRetired = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(reply.removed);\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, read instead of peeking ring seq 0 (which a\n    /// bounded ring evicts under traffic). `Ok(None)` = the broker holds the\n    /// stream but no opener is pinned (no newline yet / capture gave up).\n    /// `Err` = no such stream, or an older broker without the verb (KH-2.3 —\n    /// the dispatcher falls back to the ring peek).\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\n        self.send(\n            KIND_NET_STREAM_OPENER,\n            serde_json::to_value(NetStreamOpenerReq { stream_id })\n                .expect(\"NetStreamOpenerReq serializes\"),\n        )?;\n        loop {\n            match self.read_event()? {\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\n                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return match reply.opener_b64 {\n                        Some(b64) => decode_bytes(&b64)\n                            .map(Some)\n                            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)),\n                        None => Ok(None),\n                    };\n                }\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n                _ => continue,\n            }\n        }\n    }\n\n    /// Trigger a **planned brain-process restart** on the broker (ADR-0018 D3-3,\n    /// Q3): the broker signals its supervisor to cycle the brain child onto the\n    /// freshly-swapped binary, with `start-reason=update`. `spt update apply`\n    /// calls this after swapping the binary on disk — the seam that makes a\n    /// routine update actually run new code without a manual bounce, every\n    /// broker-held endpoint untouched at the process level.\n\n[Showing lines 1437-1508 of 2236. Use :1509 to continue]"}],"details":{"truncation":{"content":"        loop {\r\n            match self.read_event()? {\r\n                BrokerEvent::NetStreams(ev) => return Ok(ev),\r\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n                _ => continue,\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A).\r\n    /// The dispatcher calls this when a serve loop runs to completion, so no\r\n    /// future dispatcher generation rediscovers and replays the stream\r\n    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\r\n    /// answers `error` (KH-2.3 N-1 window — retirement then waits for the\r\n    /// broker to cycle onto the new binary).\r\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\r\n        self.send(\r\n            KIND_NET_STREAM_RETIRE,\r\n            serde_json::to_value(NetStreamRetireReq { stream_id })\r\n                .expect(\"NetStreamRetireReq serializes\"),\r\n        )?;\r\n        loop {\r\n            match self.read_event()? {\r\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_RETIRED => {\r\n                    let reply: NetStreamRetired = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return Ok(reply.removed);\r\n                }\r\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n                _ => continue,\r\n            }\r\n        }\r\n    }\r\n\r\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\r\n    /// classification fact, read instead of peeking ring seq 0 (which a\r\n    /// bounded ring evicts under traffic). `Ok(None)` = the broker holds the\r\n    /// stream but no opener is pinned (no newline yet / capture gave up).\r\n    /// `Err` = no such stream, or an older broker without the verb (KH-2.3 —\r\n    /// the dispatcher falls back to the ring peek).\r\n    // [impl->REQ-STREAM-OPENER-DURABLE]\r\n    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\r\n        self.send(\r\n            KIND_NET_STREAM_OPENER,\r\n            serde_json::to_value(NetStreamOpenerReq { stream_id })\r\n                .expect(\"NetStreamOpenerReq serializes\"),\r\n        )?;\r\n        loop {\r\n            match self.read_event()? {\r\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\r\n                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return match reply.opener_b64 {\r\n                        Some(b64) => decode_bytes(&b64)\r\n                            .map(Some)\r\n                            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)),\r\n                        None => Ok(None),\r\n                    };\r\n                }\r\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n                _ => continue,\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Trigger a **planned brain-process restart** on the broker (ADR-0018 D3-3,\r\n    /// Q3): the broker signals its supervisor to cycle the brain child onto the\r\n    /// freshly-swapped binary, with `start-reason=update`. `spt update apply`\r\n    /// calls this after swapping the binary on disk — the seam that makes a\r\n    /// routine update actually run new code without a manual bounce, every\r\n    /// broker-held endpoint untouched at the process level.\r","truncated":true,"truncatedBy":"lines","totalLines":800,"totalBytes":3608,"outputLines":72,"outputBytes":3608,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {\n…\n        loop {\r\n            match self.read_event()? {\r\n                BrokerEvent::NetStreams(ev) => return Ok(ev),\r\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n                _ => continue,\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Retire a completed stream row from redispatch eligibility (ADR-0038 A).\r\n    /// The dispatcher calls this when a serve loop runs to completion, so no\r\n    /// future dispatcher generation rediscovers and replays the stream\r\n    /// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker\r\n    /// answers `error` (KH-2.3 N-1 window — retirement then waits for the\r\n    /// broker to cycle onto the new binary).\r\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n    pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {\r\n        self.send(\r\n            KIND_NET_STREAM_RETIRE,\r\n            serde_json::to_value(NetStreamRetireReq { stream_id })\r\n                .expect(\"NetStreamRetireReq serializes\"),\r\n        )?;\r\n        loop {\r\n            match self.read_event()? {\r\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_RETIRED => {\r\n                    let reply: NetStreamRetired = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return Ok(reply.removed);\r\n                }\r\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n                _ => continue,\r\n            }\r\n        }\r\n    }\r\n\r\n    /// A stream's pinned opener line (ADR-0038 B) — the restart-durable\r\n    /// classification fact, read instead of peeking ring seq 0 (which a\r\n    /// bounded ring evicts under traffic). `Ok(None)` = the broker holds the\r\n    /// stream but no opener is pinned (no newline yet / capture gave up).\r\n    /// `Err` = no such stream, or an older broker without the verb (KH-2.3 —\r\n    /// the dispatcher falls back to the ring peek).\r\n    // [impl->REQ-STREAM-OPENER-DURABLE]\r\n    pub fn net_stream_opener(&mut self, stream_id: u64) -> io::Result<Option<Vec<u8>>> {\r\n        self.send(\r\n            KIND_NET_STREAM_OPENER,\r\n            serde_json::to_value(NetStreamOpenerReq { stream_id })\r\n                .expect(\"NetStreamOpenerReq serializes\"),\r\n        )?;\r\n        loop {\r\n            match self.read_event()? {\r\n                BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM_OPENER_REPLY => {\r\n                    let reply: NetStreamOpenerReply = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return match reply.opener_b64 {\r\n                        Some(b64) => decode_bytes(&b64)\r\n                            .map(Some)\r\n                            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)),\r\n                        None => Ok(None),\r\n                    };\r\n                }\r\n                BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n                _ => continue,\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Trigger a **planned brain-process restart** on the broker (ADR-0018 D3-3,\r\n    /// Q3): the broker signals its supervisor to cycle the brain child onto the\r\n    /// freshly-swapped binary, with `start-reason=update`. `spt update apply`\r\n    /// calls this after swapping the binary on disk — the seam that makes a\r\n    /// routine update actually run new code without a manual bounce, every\r\n    /// broker-held endpoint untouched at the process level.\r","startLine":1435,"lineNumbers":[1435,null,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476,1477,1478,1479,1480,1481,1482,1483,1484,1485,1486,1487,1488,1489,1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500,1501,1502,1503,1504,1505,1506,1507,1508]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2236,"totalBytes":3608,"outputLines":72,"outputBytes":3608,"shownRange":{"start":1437,"end":1508},"nextOffset":1509}}},"isError":false,"timestamp":1784247411738}}
{"type":"message","id":"a4a92d5f","parentId":"2b8e2d95","timestamp":"2026-07-17T00:16:51.759Z","message":{"role":"toolResult","toolCallId":"call_PJ6naFEsReuduwvkWEkeTLuP|fc_099a7567dadef391016a5974738b94819bb8c12b994a7f6cb8","toolName":"read","content":[{"type":"text","text":"[[requirements]]\n…\n\n[[requirements]]\nid = \"REQ-UPDATE-RESTART-SAFE-SWAP\"\ntitle = \"THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap — fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act — everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish FAILURE leaves updated adapters on old-activated core briefly — loud + operator-attended by nature of the flag). Composes with `-c/--core-only` (skip adapters leg). The 0.28.0 wedge lesson closes: the ergonomic one-step no longer picks the riskier path by default …\nrequired_stages = [\"doc\", \"impl\", \"unit\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n\n[[requirements]]\nid = \"REQ-DAEMON-REFRESH\"\ntitle = \"THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah down — today's only remedy is a full daemon bounce that kills every PTY). Broker + PTYs survive by construction (handoff invariant). Failure = the existing trial rollback semantics (old brain resumes; refresh reports loud). Gate: unit — verb routes the brain-cyc…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n\n[[requirements]]\nid = \"REQ-WHOAMI-IDENTITY-ONLY\"\ntitle = \"PROJECT-INDEX W1 (F-040, perri filing claude-spt docs/SPT-CORE-FINDINGS.md @d775b38; correctness-critical opener — the 2026-07-15 message-bodies incident root): a core IDENTITY-ONLY resolution — session -> endpoint|null — that touches NO list/registry/project/git/network path, and `spt whoami` DE-ALIASED from cmd_endpoint_list (cli.rs ~6609 aliases the full list = 100+ git children under hook deadlines). endpoint-info is DISQUALIFIED as the carrier (runs latest_project_ref). Adapters/hooks get a bounded-time identity verb; the harness-hosted adapter fallback stays deadline-vulnerable until this ships. Gate: impl — the resolver + whoami de-alias; unit — resolver returns endpoint|null with zero project derivation (assert no git spawn seam); int — whoa…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W1 build commit (todlando 2026-07-15).\n\n[[requirements]]\nid = \"REQ-PROJECT-INDEX-STORE\"\ntitle = \"PROJECT-INDEX W1 (ADR-0037, RCA .claude/reports/2026-07-10-hertz-session/03): spt-store owns the VERSIONED materialized project-index format + read path. Reader contract: read one compact versioned index, join with the local perch roster, return immediately; stale/missing renders last-known-good or '-'; NEVER fall back to synchronous git enrichment; daemon-offline readers consume the last persisted snapshot; truncated/schema-mismatched index degrades to fast reads + last-known-good, never an error stall. Gate: impl — format + store read path; unit — version/schema-mismatch/truncation degradation legs + join semantics; doc — CONTEXT.md project-index entry + STORAGE.md section. Kin REQ-PROJECT-INDEX-WRITER (the producer), ADR-0037.\"\nrequired_stages = [\"doc\", \"impl\", \"unit\"]  # FLIPPED in the PROJECT-INDEX W1 build commit (todlando 2026-07-15).\n\n[[requirements]]\nid = \"REQ-PROJECT-INDEX-WRITER\"\ntitle = \"PROJECT-INDEX W2 (ADR-0037): the daemon is the SOLE single-flight project-index writer: load persisted index at startup; ready WITHOUT warm (cold start = daemon ready + CLI fast before background completes); background reconcile with BATCHED complexity O(P+B+F+C) — enumerate branches ONCE, <=1 tree scan per changed branch, ONE derivation per distinct normalized cwd (in-process BranchStore traversal or fixed plumbing calls; backgrounding the existing 100+ process loop is REJECTED); atomic replace; last-known-good preserved on any failure; warm start with unchanged generation performs NO scan. Observability surface: generated time, source generation, pending refresh, last duration/error, endpoint/project/cwd counts, cache hits/misses, stale reads, rep…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W2 build commit (todlando 2026-07-15).\n\n[[requirements]]\nid = \"REQ-PROJECT-INDEX-INVALIDATION\"\ntitle = \"PROJECT-INDEX W2 (ADR-0037): event-driven index invalidation, debounced + coalesced: new session/cwd -> refresh that endpoint + the shared cwd cache; context-store mutation -> ONE debounced global membership refresh keyed on BRANCH-TIP FINGERPRINTS (no authoritative context-commit path exists — a writer-maintained generation counter is REJECTED because nothing maintains it); bind/start, rename, fork, purge -> affected endpoint rows; low-frequency periodic reconcile as the backstop; cwd identity refresh ONLY on path/repo-identity/.git-config change (ordinary commits do NOT alter project identity); multiple invalidations coalesce into one refresh. Gate: impl — the event set + debounce/coalesce; unit — each event class maps to its refresh scope + coale…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W2 build commit (todlando 2026-07-15).\n\n[[requirements]]\nid = \"REQ-PROJECT-INDEX-READER-CUTOVER\"\ntitle = \"PROJECT-INDEX W3 (ADR-0037): endpoint list, the picker, and endpoint-info consume the materialized index — NO git work in any user-facing read path (the O(PxB+C) fanout at cli.rs ~2954 / picker/data.rs ~456-518 dies). BEHAVIORAL PARITY is binding: precedence session-cwd -> origin-cwd -> context-recency and rendered project IDs/display names unchanged (parity suite vs the old derivation on a fixture); bare/partial run shares the indexed projection; fully-qualified --adapter+--id direct run stays picker-free; the direct-run 25s broker-session gate stays separately tested/observable. Degradation legs (git unavailable, branch malformed/locked, cwd deleted) keep fast reads. Manual latency acceptance on the 13-perch/7-branch fixture (~30s -> sub-second) +…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W3 build commit (todlando 2026-07-15).\n\n# ── CI policy (operator-ruled 2026-07-16): thin CI on docs-only PRs. ──\n[[requirements]]\nid = \"REQ-CI-DOCS-ONLY-THIN\"\ntitle = \"CI (operator-ruled 2026-07-16, from PR #8 review): a PR whose ENTIRE diff is Markdown OUTSIDE docs-site/ runs THIN CI — the heavy build/test jobs (test, n1-gate) are skipped via a changed-files classifier job. Invariants: the traceability gate ALWAYS runs (doc tags in .md files are load-bearing evidence, and a run with zero checks is unmergeable); any docs-site/ change runs the FULL pipeline (the CLI-ref/llms/book drift gates ride the test job); push events (main) always run full. The classifier is plain git diff over the PR merge commit (HEAD^1..HEAD, fetch-depth 2) — no third-party changed-files action on the self-hosted runners. Skipped-required-check note: GitHub treats an if-skipped job as satisfying required status checks, and the classifier +…\nrequired_stages = [\"impl\"]\n\n# ── REDISPATCH-TRUTH (doyle triage 2026-07-16; ADR-0038; hertz RCA .claude/reports/2026-07-16-redispatch-truth/). ──\n[[requirements]]\nid = \"REQ-HAZARD-REDISPATCH-CONTROL-STEAL\"\ntitle = \"REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 — 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream — a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int — production-path regression D1: finished historical Attach + current active Attach, same endpoint/origin; restart target brain only (real run_dispatch_loop rediscovery, NO manual re-serve — the pre-fix e2e bypass is the lesson); prove the historical stream neither takes no…\nrequired_stages = [\"doc\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). doc = KNOWN-HAZARDS 7.41; int = redispatch.rs D1 (Served-retired historical attach) + D1b gate-round-1 (finished-UNRETIRED orderings: kill gen1 mid-serve THEN detach — pre-existing-backlog/brain-down-detach shape; gen2 skips + retires-on-sight, current controller unharmed across multi-second polls) — both production run_dispatch_loop via killable dispatch_fixture generations. HEAVY at birth, all three strings.\n\n[[requirements]]\nid = \"REQ-REDISPATCH-FINISHED-RETIRE\"\ntitle = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bounded growth replaces forever-discoverable rows. Clearing the whole table on brain restart is REJECTED (destroys live streams' reconstruction facts). Gate: impl — the retirement path; unit — a finished stream is invisible to the dispatch enumeration while an activ…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16; int ADDED gate round 1; RULING gate round 2: retirement is ELIGIBILITY-VISIBILITY, never teardown — the \"lifecycle-exclude\" arm of the title, remove is REJECTED: the worker's reply send is fire-and-forget, so remove-on-retire races the still-queued reply inside the broker and resets the never-finished send half = requester torn-EOF/NoReply, proven 30/150 vs 0/150 A/B on kitsubito; physical teardown belongs to the conn-close sweep ALONE). impl = nethost retire_stream (retired flag: hidden from stream_infos + opener query, ring freed, transport/subscriber/pump untouched) + the worker finished_row_is_terminal retire-on-sight discriminator + the dispatcher Ser…\n\n[[requirements]]\nid = \"REQ-STREAM-OPENER-DURABLE\"\ntitle = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix B): stream classification identity is RESTART-DURABLE and independent of the evictable data ring — today reconstruction classifies via peek_first_line from ring seq 0, but StreamLog is a bounded 4096-transport-chunk ring (DEFAULT_STREAM_RING_CHUNKS, nethost.rs:111), so a high-traffic ACTIVE stream's opener/Request record evicts and replay classifies Unknown/Failed = active stream permanently abandoned. Pin an immutable bounded opener/classification fact (the complete first NDJSON record, or its derived family+cursor facts) OUTSIDE the data ring per broker-held inbound stream, held until stream close; recovery classifies from that metadata, never ring seq 0. Ring semantics and size untouched (enlarging the rin…\nrequired_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = StreamLog OpenerCapture (pinned outside the ring, bounded hunt) + net-stream-opener IPC + dispatcher opener-first classification (ring-peek fallback for the N-1 broker window) + serve_attach durable-Request seeding + floor_seq subscribe; unit = opener_pins_first_line_across_full_ring_roll + opener_capture_gives_up_bounded_on_a_newline_less_stream; int = redispatch.rs D2 (ring rolled via the SPT_STREAM_RING_CHUNKS test valve, gen2 resumes the SAME operator stream). HEAVY at birth.\n\n[[requirements]]\nid = \"REQ-DISPATCH-CLAIM-RETRY\"\ntitle = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED — today claimed.insert(stream_id) happens PRE-spawn (dispatch.rs:206/214) and is never cleared or retried on Unknown/Failed, so one transient worker-setup failure permanently abandons the stream. Distinguish active/retryable vs served vs finished: a transient worker-setup failure releases/requeues the claim (bounded/backoff-shaped); terminal classification outcomes stay terminal and do NOT hot-loop (show the distinction in test, not just code). Gate: impl — claim lifecycle; unit — transient failure requeues + terminal outcome does not (no hot-loop under a persistently-failing stream); int — production-path regression D3: inject ONE transient work…\nrequired_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = ClaimState lifecycle (InFlight/Terminal/Retry, should_claim + outcome_transition pure seams, MAX 3 attempts, doubling backoff); unit = transient_failure_requeues_bounded_and_terminal_never_reenters (the no-hot-loop distinction in test); int = redispatch.rs D3 (SPT_DISPATCH_INJECT_TRANSIENT_ONCE valve — one injected failure, retry recovers, injection PROVEN fired in the generation log). HEAVY at birth.\n\n# ── MESH-RECOVERY (doyle triage 2026-07-16; ADR-0039; hertz RCA .claude/reports/2026-07-10-hertz-session/02-mesh-isolation-rca.md; KNOWN-HAZARDS 7.42). Stages flip at W1 build start (traceable-per-wave-activation). ──\n[[requirements]]\nid = \"REQ-HAZARD-MESH-BOOTSTRAP-TRAP\"\ntitle = \"MESH-RECOVERY W1 (KNOWN-HAZARDS 7.42, hertz field RCA 2026-07-10 — HFENDULEAM+ENLYZEAM symmetric green-status sequester): a node holding a valid RosterEntry.address for a peer is NEVER route-less — a failed dial must not delete the only bootstrap route, and recovery must never require an already-successful connection or operator state surgery. Today: resolve_submit_addr = exact cache else id-only (never roster), PRESENCE_DIAL_FAILED unconditionally drop_seed's the cache row, and the cache refills only after a successful seed-proof exchange — one transient + stalled discovery = self-sustaining isolation, invisible (net_up true, heartbeat fresh, durable counts normal). Gate: int — production-path regression at the REAL pump resolver/failure-lifecycle …\n…\n\n\n[Showing lines 2389-2455 of 2472. Use :2456 to continue. Some lines truncated to 768 chars]"}],"details":{"truncation":{"content":"\r\n[[requirements]]\r\nid = \"REQ-UPDATE-RESTART-SAFE-SWAP\"\r\ntitle = \"THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap — fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act — everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish FAILURE leaves updated adapters on old-activated core briefly — loud + operator-attended by nature of the flag). Composes with `-c/--core-only` (skip adapters leg). The 0.28.0 wedge lesson closes: the ergonomic one-step no longer picks the riskier path by default …\nrequired_stages = [\"doc\", \"impl\", \"unit\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-DAEMON-REFRESH\"\r\ntitle = \"THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah down — today's only remedy is a full daemon bounce that kills every PTY). Broker + PTYs survive by construction (handoff invariant). Failure = the existing trial rollback semantics (old brain resumes; refresh reports loud). Gate: unit — verb routes the brain-cyc…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-WHOAMI-IDENTITY-ONLY\"\r\ntitle = \"PROJECT-INDEX W1 (F-040, perri filing claude-spt docs/SPT-CORE-FINDINGS.md @d775b38; correctness-critical opener — the 2026-07-15 message-bodies incident root): a core IDENTITY-ONLY resolution — session -> endpoint|null — that touches NO list/registry/project/git/network path, and `spt whoami` DE-ALIASED from cmd_endpoint_list (cli.rs ~6609 aliases the full list = 100+ git children under hook deadlines). endpoint-info is DISQUALIFIED as the carrier (runs latest_project_ref). Adapters/hooks get a bounded-time identity verb; the harness-hosted adapter fallback stays deadline-vulnerable until this ships. Gate: impl — the resolver + whoami de-alias; unit — resolver returns endpoint|null with zero project derivation (assert no git spawn seam); int — whoa…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W1 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-PROJECT-INDEX-STORE\"\r\ntitle = \"PROJECT-INDEX W1 (ADR-0037, RCA .claude/reports/2026-07-10-hertz-session/03): spt-store owns the VERSIONED materialized project-index format + read path. Reader contract: read one compact versioned index, join with the local perch roster, return immediately; stale/missing renders last-known-good or '-'; NEVER fall back to synchronous git enrichment; daemon-offline readers consume the last persisted snapshot; truncated/schema-mismatched index degrades to fast reads + last-known-good, never an error stall. Gate: impl — format + store read path; unit — version/schema-mismatch/truncation degradation legs + join semantics; doc — CONTEXT.md project-index entry + STORAGE.md section. Kin REQ-PROJECT-INDEX-WRITER (the producer), ADR-0037.\"\r\nrequired_stages = [\"doc\", \"impl\", \"unit\"]  # FLIPPED in the PROJECT-INDEX W1 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-PROJECT-INDEX-WRITER\"\r\ntitle = \"PROJECT-INDEX W2 (ADR-0037): the daemon is the SOLE single-flight project-index writer: load persisted index at startup; ready WITHOUT warm (cold start = daemon ready + CLI fast before background completes); background reconcile with BATCHED complexity O(P+B+F+C) — enumerate branches ONCE, <=1 tree scan per changed branch, ONE derivation per distinct normalized cwd (in-process BranchStore traversal or fixed plumbing calls; backgrounding the existing 100+ process loop is REJECTED); atomic replace; last-known-good preserved on any failure; warm start with unchanged generation performs NO scan. Observability surface: generated time, source generation, pending refresh, last duration/error, endpoint/project/cwd counts, cache hits/misses, stale reads, rep…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W2 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-PROJECT-INDEX-INVALIDATION\"\r\ntitle = \"PROJECT-INDEX W2 (ADR-0037): event-driven index invalidation, debounced + coalesced: new session/cwd -> refresh that endpoint + the shared cwd cache; context-store mutation -> ONE debounced global membership refresh keyed on BRANCH-TIP FINGERPRINTS (no authoritative context-commit path exists — a writer-maintained generation counter is REJECTED because nothing maintains it); bind/start, rename, fork, purge -> affected endpoint rows; low-frequency periodic reconcile as the backstop; cwd identity refresh ONLY on path/repo-identity/.git-config change (ordinary commits do NOT alter project identity); multiple invalidations coalesce into one refresh. Gate: impl — the event set + debounce/coalesce; unit — each event class maps to its refresh scope + coale…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W2 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-PROJECT-INDEX-READER-CUTOVER\"\r\ntitle = \"PROJECT-INDEX W3 (ADR-0037): endpoint list, the picker, and endpoint-info consume the materialized index — NO git work in any user-facing read path (the O(PxB+C) fanout at cli.rs ~2954 / picker/data.rs ~456-518 dies). BEHAVIORAL PARITY is binding: precedence session-cwd -> origin-cwd -> context-recency and rendered project IDs/display names unchanged (parity suite vs the old derivation on a fixture); bare/partial run shares the indexed projection; fully-qualified --adapter+--id direct run stays picker-free; the direct-run 25s broker-session gate stays separately tested/observable. Degradation legs (git unavailable, branch malformed/locked, cwd deleted) keep fast reads. Manual latency acceptance on the 13-perch/7-branch fixture (~30s -> sub-second) +…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W3 build commit (todlando 2026-07-15).\r\n\r\n# ── CI policy (operator-ruled 2026-07-16): thin CI on docs-only PRs. ──\r\n[[requirements]]\r\nid = \"REQ-CI-DOCS-ONLY-THIN\"\r\ntitle = \"CI (operator-ruled 2026-07-16, from PR #8 review): a PR whose ENTIRE diff is Markdown OUTSIDE docs-site/ runs THIN CI — the heavy build/test jobs (test, n1-gate) are skipped via a changed-files classifier job. Invariants: the traceability gate ALWAYS runs (doc tags in .md files are load-bearing evidence, and a run with zero checks is unmergeable); any docs-site/ change runs the FULL pipeline (the CLI-ref/llms/book drift gates ride the test job); push events (main) always run full. The classifier is plain git diff over the PR merge commit (HEAD^1..HEAD, fetch-depth 2) — no third-party changed-files action on the self-hosted runners. Skipped-required-check note: GitHub treats an if-skipped job as satisfying required status checks, and the classifier +…\nrequired_stages = [\"impl\"]\r\n\r\n# ── REDISPATCH-TRUTH (doyle triage 2026-07-16; ADR-0038; hertz RCA .claude/reports/2026-07-16-redispatch-truth/). ──\r\n[[requirements]]\r\nid = \"REQ-HAZARD-REDISPATCH-CONTROL-STEAL\"\r\ntitle = \"REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 — 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream — a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int — production-path regression D1: finished historical Attach + current active Attach, same endpoint/origin; restart target brain only (real run_dispatch_loop rediscovery, NO manual re-serve — the pre-fix e2e bypass is the lesson); prove the historical stream neither takes no…\nrequired_stages = [\"doc\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). doc = KNOWN-HAZARDS 7.41; int = redispatch.rs D1 (Served-retired historical attach) + D1b gate-round-1 (finished-UNRETIRED orderings: kill gen1 mid-serve THEN detach — pre-existing-backlog/brain-down-detach shape; gen2 skips + retires-on-sight, current controller unharmed across multi-second polls) — both production run_dispatch_loop via killable dispatch_fixture generations. HEAVY at birth, all three strings.\r\n\r\n[[requirements]]\r\nid = \"REQ-REDISPATCH-FINISHED-RETIRE\"\r\ntitle = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bounded growth replaces forever-discoverable rows. Clearing the whole table on brain restart is REJECTED (destroys live streams' reconstruction facts). Gate: impl — the retirement path; unit — a finished stream is invisible to the dispatch enumeration while an activ…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16; int ADDED gate round 1; RULING gate round 2: retirement is ELIGIBILITY-VISIBILITY, never teardown — the \"lifecycle-exclude\" arm of the title, remove is REJECTED: the worker's reply send is fire-and-forget, so remove-on-retire races the still-queued reply inside the broker and resets the never-finished send half = requester torn-EOF/NoReply, proven 30/150 vs 0/150 A/B on kitsubito; physical teardown belongs to the conn-close sweep ALONE). impl = nethost retire_stream (retired flag: hidden from stream_infos + opener query, ring freed, transport/subscriber/pump untouched) + the worker finished_row_is_terminal retire-on-sight discriminator + the dispatcher Ser…\n\r\n[[requirements]]\r\nid = \"REQ-STREAM-OPENER-DURABLE\"\r\ntitle = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix B): stream classification identity is RESTART-DURABLE and independent of the evictable data ring — today reconstruction classifies via peek_first_line from ring seq 0, but StreamLog is a bounded 4096-transport-chunk ring (DEFAULT_STREAM_RING_CHUNKS, nethost.rs:111), so a high-traffic ACTIVE stream's opener/Request record evicts and replay classifies Unknown/Failed = active stream permanently abandoned. Pin an immutable bounded opener/classification fact (the complete first NDJSON record, or its derived family+cursor facts) OUTSIDE the data ring per broker-held inbound stream, held until stream close; recovery classifies from that metadata, never ring seq 0. Ring semantics and size untouched (enlarging the rin…\nrequired_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = StreamLog OpenerCapture (pinned outside the ring, bounded hunt) + net-stream-opener IPC + dispatcher opener-first classification (ring-peek fallback for the N-1 broker window) + serve_attach durable-Request seeding + floor_seq subscribe; unit = opener_pins_first_line_across_full_ring_roll + opener_capture_gives_up_bounded_on_a_newline_less_stream; int = redispatch.rs D2 (ring rolled via the SPT_STREAM_RING_CHUNKS test valve, gen2 resumes the SAME operator stream). HEAVY at birth.\r\n\r\n[[requirements]]\r\nid = \"REQ-DISPATCH-CLAIM-RETRY\"\r\ntitle = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED — today claimed.insert(stream_id) happens PRE-spawn (dispatch.rs:206/214) and is never cleared or retried on Unknown/Failed, so one transient worker-setup failure permanently abandons the stream. Distinguish active/retryable vs served vs finished: a transient worker-setup failure releases/requeues the claim (bounded/backoff-shaped); terminal classification outcomes stay terminal and do NOT hot-loop (show the distinction in test, not just code). Gate: impl — claim lifecycle; unit — transient failure requeues + terminal outcome does not (no hot-loop under a persistently-failing stream); int — production-path regression D3: inject ONE transient work…\nrequired_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = ClaimState lifecycle (InFlight/Terminal/Retry, should_claim + outcome_transition pure seams, MAX 3 attempts, doubling backoff); unit = transient_failure_requeues_bounded_and_terminal_never_reenters (the no-hot-loop distinction in test); int = redispatch.rs D3 (SPT_DISPATCH_INJECT_TRANSIENT_ONCE valve — one injected failure, retry recovers, injection PROVEN fired in the generation log). HEAVY at birth.\r\n\r\n# ── MESH-RECOVERY (doyle triage 2026-07-16; ADR-0039; hertz RCA .claude/reports/2026-07-10-hertz-session/02-mesh-isolation-rca.md; KNOWN-HAZARDS 7.42). Stages flip at W1 build start (traceable-per-wave-activation). ──\r\n[[requirements]]\r\nid = \"REQ-HAZARD-MESH-BOOTSTRAP-TRAP\"\r\ntitle = \"MESH-RECOVERY W1 (KNOWN-HAZARDS 7.42, hertz field RCA 2026-07-10 — HFENDULEAM+ENLYZEAM symmetric green-status sequester): a node holding a valid RosterEntry.address for a peer is NEVER route-less — a failed dial must not delete the only bootstrap route, and recovery must never require an already-successful connection or operator state surgery. Today: resolve_submit_addr = exact cache else id-only (never roster), PRESENCE_DIAL_FAILED unconditionally drop_seed's the cache row, and the cache refills only after a successful seed-proof exchange — one transient + stalled discovery = self-sustaining isolation, invisible (net_up true, heartbeat fresh, durable counts normal). Gate: int — production-path regression at the REAL pump resolver/failure-lifecycle …","truncated":true,"truncatedBy":"lines","totalLines":84,"totalBytes":18803,"outputLines":67,"outputBytes":18803,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"[[requirements]]\n…\n\r\n[[requirements]]\r\nid = \"REQ-UPDATE-RESTART-SAFE-SWAP\"\r\ntitle = \"THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap — fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act — everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish FAILURE leaves updated adapters on old-activated core briefly — loud + operator-attended by nature of the flag). Composes with `-c/--core-only` (skip adapters leg). The 0.28.0 wedge lesson closes: the ergonomic one-step no longer picks the riskier path by default …\nrequired_stages = [\"doc\", \"impl\", \"unit\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-DAEMON-REFRESH\"\r\ntitle = \"THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah down — today's only remedy is a full daemon bounce that kills every PTY). Broker + PTYs survive by construction (handoff invariant). Failure = the existing trial rollback semantics (old brain resumes; refresh reports loud). Gate: unit — verb routes the brain-cyc…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-WHOAMI-IDENTITY-ONLY\"\r\ntitle = \"PROJECT-INDEX W1 (F-040, perri filing claude-spt docs/SPT-CORE-FINDINGS.md @d775b38; correctness-critical opener — the 2026-07-15 message-bodies incident root): a core IDENTITY-ONLY resolution — session -> endpoint|null — that touches NO list/registry/project/git/network path, and `spt whoami` DE-ALIASED from cmd_endpoint_list (cli.rs ~6609 aliases the full list = 100+ git children under hook deadlines). endpoint-info is DISQUALIFIED as the carrier (runs latest_project_ref). Adapters/hooks get a bounded-time identity verb; the harness-hosted adapter fallback stays deadline-vulnerable until this ships. Gate: impl — the resolver + whoami de-alias; unit — resolver returns endpoint|null with zero project derivation (assert no git spawn seam); int — whoa…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W1 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-PROJECT-INDEX-STORE\"\r\ntitle = \"PROJECT-INDEX W1 (ADR-0037, RCA .claude/reports/2026-07-10-hertz-session/03): spt-store owns the VERSIONED materialized project-index format + read path. Reader contract: read one compact versioned index, join with the local perch roster, return immediately; stale/missing renders last-known-good or '-'; NEVER fall back to synchronous git enrichment; daemon-offline readers consume the last persisted snapshot; truncated/schema-mismatched index degrades to fast reads + last-known-good, never an error stall. Gate: impl — format + store read path; unit — version/schema-mismatch/truncation degradation legs + join semantics; doc — CONTEXT.md project-index entry + STORAGE.md section. Kin REQ-PROJECT-INDEX-WRITER (the producer), ADR-0037.\"\r\nrequired_stages = [\"doc\", \"impl\", \"unit\"]  # FLIPPED in the PROJECT-INDEX W1 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-PROJECT-INDEX-WRITER\"\r\ntitle = \"PROJECT-INDEX W2 (ADR-0037): the daemon is the SOLE single-flight project-index writer: load persisted index at startup; ready WITHOUT warm (cold start = daemon ready + CLI fast before background completes); background reconcile with BATCHED complexity O(P+B+F+C) — enumerate branches ONCE, <=1 tree scan per changed branch, ONE derivation per distinct normalized cwd (in-process BranchStore traversal or fixed plumbing calls; backgrounding the existing 100+ process loop is REJECTED); atomic replace; last-known-good preserved on any failure; warm start with unchanged generation performs NO scan. Observability surface: generated time, source generation, pending refresh, last duration/error, endpoint/project/cwd counts, cache hits/misses, stale reads, rep…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W2 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-PROJECT-INDEX-INVALIDATION\"\r\ntitle = \"PROJECT-INDEX W2 (ADR-0037): event-driven index invalidation, debounced + coalesced: new session/cwd -> refresh that endpoint + the shared cwd cache; context-store mutation -> ONE debounced global membership refresh keyed on BRANCH-TIP FINGERPRINTS (no authoritative context-commit path exists — a writer-maintained generation counter is REJECTED because nothing maintains it); bind/start, rename, fork, purge -> affected endpoint rows; low-frequency periodic reconcile as the backstop; cwd identity refresh ONLY on path/repo-identity/.git-config change (ordinary commits do NOT alter project identity); multiple invalidations coalesce into one refresh. Gate: impl — the event set + debounce/coalesce; unit — each event class maps to its refresh scope + coale…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W2 build commit (todlando 2026-07-15).\r\n\r\n[[requirements]]\r\nid = \"REQ-PROJECT-INDEX-READER-CUTOVER\"\r\ntitle = \"PROJECT-INDEX W3 (ADR-0037): endpoint list, the picker, and endpoint-info consume the materialized index — NO git work in any user-facing read path (the O(PxB+C) fanout at cli.rs ~2954 / picker/data.rs ~456-518 dies). BEHAVIORAL PARITY is binding: precedence session-cwd -> origin-cwd -> context-recency and rendered project IDs/display names unchanged (parity suite vs the old derivation on a fixture); bare/partial run shares the indexed projection; fully-qualified --adapter+--id direct run stays picker-free; the direct-run 25s broker-session gate stays separately tested/observable. Degradation legs (git unavailable, branch malformed/locked, cwd deleted) keep fast reads. Manual latency acceptance on the 13-perch/7-branch fixture (~30s -> sub-second) +…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the PROJECT-INDEX W3 build commit (todlando 2026-07-15).\r\n\r\n# ── CI policy (operator-ruled 2026-07-16): thin CI on docs-only PRs. ──\r\n[[requirements]]\r\nid = \"REQ-CI-DOCS-ONLY-THIN\"\r\ntitle = \"CI (operator-ruled 2026-07-16, from PR #8 review): a PR whose ENTIRE diff is Markdown OUTSIDE docs-site/ runs THIN CI — the heavy build/test jobs (test, n1-gate) are skipped via a changed-files classifier job. Invariants: the traceability gate ALWAYS runs (doc tags in .md files are load-bearing evidence, and a run with zero checks is unmergeable); any docs-site/ change runs the FULL pipeline (the CLI-ref/llms/book drift gates ride the test job); push events (main) always run full. The classifier is plain git diff over the PR merge commit (HEAD^1..HEAD, fetch-depth 2) — no third-party changed-files action on the self-hosted runners. Skipped-required-check note: GitHub treats an if-skipped job as satisfying required status checks, and the classifier +…\nrequired_stages = [\"impl\"]\r\n\r\n# ── REDISPATCH-TRUTH (doyle triage 2026-07-16; ADR-0038; hertz RCA .claude/reports/2026-07-16-redispatch-truth/). ──\r\n[[requirements]]\r\nid = \"REQ-HAZARD-REDISPATCH-CONTROL-STEAL\"\r\ntitle = \"REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 — 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream — a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int — production-path regression D1: finished historical Attach + current active Attach, same endpoint/origin; restart target brain only (real run_dispatch_loop rediscovery, NO manual re-serve — the pre-fix e2e bypass is the lesson); prove the historical stream neither takes no…\nrequired_stages = [\"doc\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). doc = KNOWN-HAZARDS 7.41; int = redispatch.rs D1 (Served-retired historical attach) + D1b gate-round-1 (finished-UNRETIRED orderings: kill gen1 mid-serve THEN detach — pre-existing-backlog/brain-down-detach shape; gen2 skips + retires-on-sight, current controller unharmed across multi-second polls) — both production run_dispatch_loop via killable dispatch_fixture generations. HEAVY at birth, all three strings.\r\n\r\n[[requirements]]\r\nid = \"REQ-REDISPATCH-FINISHED-RETIRE\"\r\ntitle = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bounded growth replaces forever-discoverable rows. Clearing the whole table on brain restart is REJECTED (destroys live streams' reconstruction facts). Gate: impl — the retirement path; unit — a finished stream is invisible to the dispatch enumeration while an activ…\nrequired_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16; int ADDED gate round 1; RULING gate round 2: retirement is ELIGIBILITY-VISIBILITY, never teardown — the \"lifecycle-exclude\" arm of the title, remove is REJECTED: the worker's reply send is fire-and-forget, so remove-on-retire races the still-queued reply inside the broker and resets the never-finished send half = requester torn-EOF/NoReply, proven 30/150 vs 0/150 A/B on kitsubito; physical teardown belongs to the conn-close sweep ALONE). impl = nethost retire_stream (retired flag: hidden from stream_infos + opener query, ring freed, transport/subscriber/pump untouched) + the worker finished_row_is_terminal retire-on-sight discriminator + the dispatcher Ser…\n\r\n[[requirements]]\r\nid = \"REQ-STREAM-OPENER-DURABLE\"\r\ntitle = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix B): stream classification identity is RESTART-DURABLE and independent of the evictable data ring — today reconstruction classifies via peek_first_line from ring seq 0, but StreamLog is a bounded 4096-transport-chunk ring (DEFAULT_STREAM_RING_CHUNKS, nethost.rs:111), so a high-traffic ACTIVE stream's opener/Request record evicts and replay classifies Unknown/Failed = active stream permanently abandoned. Pin an immutable bounded opener/classification fact (the complete first NDJSON record, or its derived family+cursor facts) OUTSIDE the data ring per broker-held inbound stream, held until stream close; recovery classifies from that metadata, never ring seq 0. Ring semantics and size untouched (enlarging the rin…\nrequired_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = StreamLog OpenerCapture (pinned outside the ring, bounded hunt) + net-stream-opener IPC + dispatcher opener-first classification (ring-peek fallback for the N-1 broker window) + serve_attach durable-Request seeding + floor_seq subscribe; unit = opener_pins_first_line_across_full_ring_roll + opener_capture_gives_up_bounded_on_a_newline_less_stream; int = redispatch.rs D2 (ring rolled via the SPT_STREAM_RING_CHUNKS test valve, gen2 resumes the SAME operator stream). HEAVY at birth.\r\n\r\n[[requirements]]\r\nid = \"REQ-DISPATCH-CLAIM-RETRY\"\r\ntitle = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED — today claimed.insert(stream_id) happens PRE-spawn (dispatch.rs:206/214) and is never cleared or retried on Unknown/Failed, so one transient worker-setup failure permanently abandons the stream. Distinguish active/retryable vs served vs finished: a transient worker-setup failure releases/requeues the claim (bounded/backoff-shaped); terminal classification outcomes stay terminal and do NOT hot-loop (show the distinction in test, not just code). Gate: impl — claim lifecycle; unit — transient failure requeues + terminal outcome does not (no hot-loop under a persistently-failing stream); int — production-path regression D3: inject ONE transient work…\nrequired_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = ClaimState lifecycle (InFlight/Terminal/Retry, should_claim + outcome_transition pure seams, MAX 3 attempts, doubling backoff); unit = transient_failure_requeues_bounded_and_terminal_never_reenters (the no-hot-loop distinction in test); int = redispatch.rs D3 (SPT_DISPATCH_INJECT_TRANSIENT_ONCE valve — one injected failure, retry recovers, injection PROVEN fired in the generation log). HEAVY at birth.\r\n\r\n# ── MESH-RECOVERY (doyle triage 2026-07-16; ADR-0039; hertz RCA .claude/reports/2026-07-10-hertz-session/02-mesh-isolation-rca.md; KNOWN-HAZARDS 7.42). Stages flip at W1 build start (traceable-per-wave-activation). ──\r\n[[requirements]]\r\nid = \"REQ-HAZARD-MESH-BOOTSTRAP-TRAP\"\r\ntitle = \"MESH-RECOVERY W1 (KNOWN-HAZARDS 7.42, hertz field RCA 2026-07-10 — HFENDULEAM+ENLYZEAM symmetric green-status sequester): a node holding a valid RosterEntry.address for a peer is NEVER route-less — a failed dial must not delete the only bootstrap route, and recovery must never require an already-successful connection or operator state surgery. Today: resolve_submit_addr = exact cache else id-only (never roster), PRESENCE_DIAL_FAILED unconditionally drop_seed's the cache row, and the cache refills only after a successful seed-proof exchange — one transient + stalled discovery = self-sustaining isolation, invisible (net_up true, heartbeat fresh, durable counts normal). Gate: int — production-path regression at the REAL pump resolver/failure-lifecycle …\n…\n","startLine":2385,"lineNumbers":[2385,null,2389,2390,2391,2392,2393,2394,2395,2396,2397,2398,2399,2400,2401,2402,2403,2404,2405,2406,2407,2408,2409,2410,2411,2412,2413,2414,2415,2416,2417,2418,2419,2420,2421,2422,2423,2424,2425,2426,2427,2428,2429,2430,2431,2432,2433,2434,2435,2436,2437,2438,2439,2440,2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453,2454,2455,null,2457]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\traceable-reqs.toml"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2472,"totalBytes":18803,"outputLines":67,"outputBytes":18803,"shownRange":{"start":2389,"end":2455},"nextOffset":2456},"limits":{"columnTruncated":{"maxColumn":768}}}},"isError":false,"timestamp":1784247411759}}
{"type":"message","id":"6b0197ef","parentId":"a4a92d5f","timestamp":"2026-07-17T00:17:06.437Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing broker image refresh behavior**\n\n**Verifying daemon restart behavior inconsistency**"},{"type":"toolCall","id":"call_witme2GVSm3ROqGWugXY5Brc|fc_099a7567dadef391016a5974826260819ba721e3f95b4a385d","name":"grep","arguments":{"i":"Tracing restart semantics","pattern":"UpdateCmd|Restart|update --restart|cmd_update_restart|apply.*finish|restart_safe|restart_safe_swap|update_restart","path":"crates/spt/src; crates/spt/tests; CHANGELOG.md; docs-site; CONTEXT.md","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_aVMZ0BXJMgsk5a9FC6zmeDN1|fc_099a7567dadef391016a5974826270819bbd4eea7b245cdf81","name":"read","arguments":{"i":"Reading update CLI implementation","path":"crates/spt/src/cli.rs","selector":"3860-4095"}},{"type":"toolCall","id":"call_N5Ftou6pFssyuaSbMvU0n4Wy|fc_099a7567dadef391016a597482627c819ba4874bb3202dacc6","name":"read","arguments":{"i":"Reading changelog update section","path":"CHANGELOG.md","selector":"85-170"}},{"type":"toolCall","id":"call_8NXqliczbp4b4txzrlCYRYqY|fc_099a7567dadef391016a5974826280819b8515da398ec8f775","name":"grep","arguments":{"i":"Finding full restart tests","pattern":"UPDATE-RESTART-SAFE-SWAP|update --restart|apply --finish|FINISH_ENDPOINT_SURVIVAL|finish.*surviv","path":"crates; traceable-reqs.toml; docs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_eInPa3ekq9ydeTOQgY3M9O1E|fc_099a7567dadef391016a5974826288819b868b13da61c98222","name":"read","arguments":{"i":"Reading N1 compatibility test","path":"crates/spt/tests/n1_pairing.rs","selector":"1-190"}},{"type":"toolCall","id":"call_p3umcPaW57bhHL3xkwxYVkWJ|fc_099a7567dadef391016a5974826290819ba18657f0f32b9891","name":"read","arguments":{"i":"Reading session survival contract","path":"CONTEXT.md","selector":"560-590"}},{"type":"toolCall","id":"call_EW4IQgzivPTD3f7bFEBmVZeu|fc_099a7567dadef391016a5974826294819b80013c8db9cb023c","name":"grep","arguments":{"i":"Checking release changelog tests","pattern":"CHANGELOG|release notes|version ladder|contiguous|notes-file|0\\.33\\.0","path":"crates/xtask; .github; docs; tests","selector":null,"case":false,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":35635,"output":722,"cacheRead":103936,"cacheWrite":0,"totalTokens":140293,"reasoningTokens":332,"cost":{"input":0.17817500000000003,"output":0.021660000000000002,"cacheRead":0.051968,"cacheWrite":0,"total":0.25180300000000005}},"stopReason":"toolUse","timestamp":1784247411874,"responseId":"resp_099a7567dadef391016a5974741ac4819baaf82fb035a4fb48","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a59747559c4819baf349206926c0a46","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXSCZojnelg7HXSE-8zLa3MN4Wrv8jqzSdDMxybLxVo72N8BH5zC7kcAlPsgq0iB7F7ozfrBHXpd3RlusoOswtWkMcGLi4JJXl0x_F9vagQ-ndZPHWoSz80SfWjqKRaPv7LCdBXrL5Uc9M-6fn_Ng0nBvGdHjLweS5QtfLLDECfjCwLcIpA6c-ppxXAr075CY3KTbIxlvW8aCA3MlDVMsvEFhnGddr0MHN8x05oEPabxRqvvPUrDHjjNQTUtRMXVIjHryqstC4A8pcyL6Imqm59X9IorxlZymKGovKKRJhQSeVReeX-Qsw1hCZp-E50iep6uu5GK_IRsNVynbyU3uNVOlRQkRkKO1VaQxdWoHUB9MQAtOVag9V8WA_dNC1O-v4y8pqMlvwEUplIO2ooqTyGADiTalWKUX9CYRQGd31z8caxwgFqnUYfTK_PLgu2sonsmWtvBd2DuHrPduj0rDjamvfxXNFtsOxX-rew_GZMoCycL_l3OV4PO8ldcshJsmfAUDHIHUDqEgvmysu9RZAs_kpl0fR-NgcSxJ3xcTNlBw2e9GlTFC1x9ESLc_AEDDVLDxR9-RhRZDDylmw-L2QkTXBA3IjzRpjcnJEgOZsDiawphEr0FAyB4Y368bSi33TqrXyxoVSyyV2qWfvkDJKG292LSAn5kXIs69TGMLiZnIn72fLeMEoUj4IxNpqw-azlqEr5O1L2MEY0-qIaH7ke6tjiQhXH1o9yC_bkh-JNJEiqOGuLvnoB8XUjwHqEF-3NHd4H2Hrxkg2i_y39Bo_9e-4CqrWN1szsrMhqpDisfKn9w5k7mOK7fvPKJyNsmdDoVMCfaaipk0udO_O8JnOC9SK22AZqyd0ZkXENJx4xGUtHHkjFEJ9qAoUkGE4ArmslVR98MrFYM621XcU5bmU1o9XU2bYgcwzYHYV6V7Ln5-oBASHop6mBH43yQL_QWzP6DgrX9CfB4yT0j1ZR7X_VQTvSKos48HmX8v8-OHo2CI_XA01rLL5xRsVLRDQABshJpC9R7_eYOaIMK8McKfZYAN--3uowgQf1ri6n2ztK0ynqqrne5UShbdfm4GZZk7g5Sn3dQhy0-OFJ-9uPwcSokCw2NKoXVxVE5UOWCk0e5kgcxbYTGsg2uRhqcNXmwBu9vuCG1dmQWphm92cJznzpq9X6KL6y8k9EtJd18KYecDggjtUBOpyPTJQhTOex2tLpJ4k5s02Gq4vn-8BY3ApUpTaDWzMH1-FJLblZed_dRcpncEZ0iv-kQUhIAH2BuCDbG47UKan5yScjzq_rXppLrWjwoZC1ZBgyQ9KRLmmpEVT-aJ8dp5m7Khk5CJSNq334whDMxqi4j3ToxdSujfQDupHn_402ACrD1bQ3EV9Kh1ymiHBZmQ81o0JV2acGQdwDkAqLwzYcMSPEvOp-qzZyus4D_o5vIw1XqZ9JYOe5yXg8Bd_R1cJbs3t1kwYXTCJUnSU44vewPqfSI8bO9-TahO6dhmIRzDu9apLmGBlzD9-Ea5OkAltzRgEi_DtQkYMMLlKADSL9bNb82aggSgbwoMj-BjkMX-wVq8Lhpw2VLH0ZE_NCiNnJBOyVRTb8CLdDhJBXjgjoNmCSjBRji8Syp3AiI8MJZ4aplNeb2L42MJREvyAFsT0RXviMraWbl4-XP5YFxjzCH7RUfIci7UQh4mfiTAzHt3P0-hcvNkK77y7d2GAl4MIjCNsfn7X1OP-OMxX200nBVWXP2i_AZqatACWku7qIzbScdh9QMhv28GGN0nBfV-116qCTGoJU6agnRryfjCbKl2K3uHMHqBtsfuM8jkkg9Xndy4O9KcGfyinmhzFW6nq9oiq-Jvwqxi40IFQNY93eUiz2cmcrW8nn5UeaRlPq7LWqnUtCVOeVyG2du1RgfRl1-QcFgILL2HY8eLWzJWbCvlAW1dxgDnXgK6w80OIT9uHl8yQaig_XjTzxgikyu9s0HrcuKm35vqjcCOtFF97Bl5HN0IKJxtBBpOBaxDizcW2QTXjBzw0Gwnqg7CJM4e-1REzCvW2Arze9oxF5z2jI3BRJbjECmCI2Q4PdILBBet46pSPq-jUFKsumHqWrYw3YZpU9jyAnyeZpnJ59ZwhCOTYtQNHE7wo_XU8KJ9vhRKg5lGEDFAXumK1WDnCSDDW63KUWZEaZbWBSQ2jdM8xoBaSafWw1ywD1-h9c55KfqTQgnZQE_fbUuUUneTbmCUt3PtZ9Y3FdQdeinQdcZL1ijUwNNpKcie4-rUZqfNhlpmkleQ9nSsId5YCIy07cT_2C76Bcn10scrYTy2c8hQRFV2yIA6cMLxlYpa8YY1m2SMIRYurkVnkYxIH8w_mX6M2MLYsIWsLSWCLjCZv8w8VtUodjoHoKhjOoJLAXfNUdqd73FE3LuaZkvbpUGpd3B1Zz2P2ojCkWuBBKVhMXybr2VIvqtCMwMRIIm6fXYdZ_oJyD6KwG6r7R37F9po8JzJTAQ2yeFvXF87pbMqiTEDmi2m70yrZo7Y0UyrZ5cpL7QK_QUi8exVUaOyONYHzk6HoSSyy84I9b0oVWhnBwM4BBzO-i5-pUQicrwEOpLR3plRQyCxn4Y6CdQy0wpXRVeLkoB3JQKEcb0Jn15wKHdSmwsncvCoJ9x_CJ6hFMqwyylD9ALXzg6XTgzpBsJD8a1XRUV2bqndkVn_T_8qYNkvpvunoplsyVy6sRkESKIxapGUdanfC1DkApa2tkWBGipsu2ghmg1ILptvRPNp7rEqNFcb9WHDcF9Y9cpD9sqIcOKEWulxSiaIREcVxq8BdOLbKjZGrq2WD6ZMUFyqzSlTiMGkhIgX5RjJNg3DTj-40c8oHNtQi_MY5xR2S8tYdlt1RPLb2FY9x_Caj37Xhyw9YNhj07coxlP8SZB4Rsh91Pb-TQgyeyr08uToYmwkpjQV18SowQpLerYx6pQFcZQm7-4ii3CDDFUFJrF47oVkVKKG5r43fy0DIb71pe0U-4_IHOi_OZFFQb9PIwnLq-GBxjTYVGqkZZYmjSZTnclrPMDn0x8lkJiw_tKhRA=","summary":[{"type":"summary_text","text":"**Analyzing broker image refresh behavior**"},{"type":"summary_text","text":"**Verifying daemon restart behavior inconsistency**"}]},{"id":"fc_099a7567dadef391016a5974826260819ba721e3f95b4a385d","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing restart semantics\",\"pattern\":\"UpdateCmd|Restart|update --restart|cmd_update_restart|apply.*finish|restart_safe|restart_safe_swap|update_restart\",\"path\":\"crates/spt/src; crates/spt/tests; CHANGELOG.md; docs-site; CONTEXT.md\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_witme2GVSm3ROqGWugXY5Brc","name":"grep"},{"id":"fc_099a7567dadef391016a5974826270819bbd4eea7b245cdf81","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading update CLI implementation\",\"path\":\"crates/spt/src/cli.rs\",\"selector\":\"3860-4095\"}","call_id":"call_aVMZ0BXJMgsk5a9FC6zmeDN1","name":"read"},{"id":"fc_099a7567dadef391016a597482627c819ba4874bb3202dacc6","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading changelog update section\",\"path\":\"CHANGELOG.md\",\"selector\":\"85-170\"}","call_id":"call_N5Ftou6pFssyuaSbMvU0n4Wy","name":"read"},{"id":"fc_099a7567dadef391016a5974826280819b8515da398ec8f775","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding full restart tests\",\"pattern\":\"UPDATE-RESTART-SAFE-SWAP|update --restart|apply --finish|FINISH_ENDPOINT_SURVIVAL|finish.*surviv\",\"path\":\"crates; traceable-reqs.toml; docs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_8NXqliczbp4b4txzrlCYRYqY","name":"grep"},{"id":"fc_099a7567dadef391016a5974826288819b868b13da61c98222","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading N1 compatibility test\",\"path\":\"crates/spt/tests/n1_pairing.rs\",\"selector\":\"1-190\"}","call_id":"call_eInPa3ekq9ydeTOQgY3M9O1E","name":"read"},{"id":"fc_099a7567dadef391016a5974826290819ba18657f0f32b9891","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading session survival contract\",\"path\":\"CONTEXT.md\",\"selector\":\"560-590\"}","call_id":"call_p3umcPaW57bhHL3xkwxYVkWJ","name":"read"},{"id":"fc_099a7567dadef391016a5974826294819b80013c8db9cb023c","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking release changelog tests\",\"pattern\":\"CHANGELOG|release notes|version ladder|contiguous|notes-file|0\\\\.33\\\\.0\",\"path\":\"crates/xtask; .github; docs; tests\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_EW4IQgzivPTD3f7bFEBmVZeu","name":"grep"}]},"duration":14562.029200002551,"ttft":1470.521099999547,"contextSnapshot":{"promptTokens":139571,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_witme2GVSm3ROqGWugXY5Brc|fc_099a7567dadef391016a5974826260819ba721e3f95b4a385d","toolName":"grep","startedAt":"2026-07-17T00:17:06.437Z","args":{"path":"crates/spt/src; crates/spt/tests; CHANGELOG.md; docs-site; CONTEXT.md"},"intent":"Tracing restart semantics"},"id":"616430e2","parentId":"6b0197ef","timestamp":"2026-07-17T00:17:06.437Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_aVMZ0BXJMgsk5a9FC6zmeDN1|fc_099a7567dadef391016a5974826270819bbd4eea7b245cdf81","toolName":"read","startedAt":"2026-07-17T00:17:06.437Z","args":{"path":"crates/spt/src/cli.rs"},"intent":"Reading update CLI implementation"},"id":"a93fa6f5","parentId":"616430e2","timestamp":"2026-07-17T00:17:06.437Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_N5Ftou6pFssyuaSbMvU0n4Wy|fc_099a7567dadef391016a597482627c819ba4874bb3202dacc6","toolName":"read","startedAt":"2026-07-17T00:17:06.437Z","args":{"path":"CHANGELOG.md"},"intent":"Reading changelog update section"},"id":"8d3e9bf6","parentId":"a93fa6f5","timestamp":"2026-07-17T00:17:06.437Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_8NXqliczbp4b4txzrlCYRYqY|fc_099a7567dadef391016a5974826280819b8515da398ec8f775","toolName":"grep","startedAt":"2026-07-17T00:17:06.437Z","args":{"path":"crates; traceable-reqs.toml; docs"},"intent":"Finding full restart tests"},"id":"9ac91b33","parentId":"8d3e9bf6","timestamp":"2026-07-17T00:17:06.437Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_eInPa3ekq9ydeTOQgY3M9O1E|fc_099a7567dadef391016a5974826288819b868b13da61c98222","toolName":"read","startedAt":"2026-07-17T00:17:06.437Z","args":{"path":"crates/spt/tests/n1_pairing.rs"},"intent":"Reading N1 compatibility test"},"id":"c9719511","parentId":"9ac91b33","timestamp":"2026-07-17T00:17:06.437Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_p3umcPaW57bhHL3xkwxYVkWJ|fc_099a7567dadef391016a5974826290819ba18657f0f32b9891","toolName":"read","startedAt":"2026-07-17T00:17:06.437Z","args":{"path":"CONTEXT.md"},"intent":"Reading session survival contract"},"id":"367be36b","parentId":"c9719511","timestamp":"2026-07-17T00:17:06.437Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_EW4IQgzivPTD3f7bFEBmVZeu|fc_099a7567dadef391016a5974826294819b80013c8db9cb023c","toolName":"grep","startedAt":"2026-07-17T00:17:06.437Z","args":{"path":"crates/xtask; .github; docs; tests"},"intent":"Checking release changelog tests"},"id":"4be0fa12","parentId":"367be36b","timestamp":"2026-07-17T00:17:06.437Z"}
{"type":"message","id":"c08d50c7","parentId":"4be0fa12","timestamp":"2026-07-17T00:17:06.440Z","message":{"role":"toolResult","toolCallId":"call_eInPa3ekq9ydeTOQgY3M9O1E|fc_099a7567dadef391016a5974826288819b868b13da61c98222","toolName":"read","content":[{"type":"text","text":"//! Restoration D7-2 (ADR-0018 V6) — **new-brain × old-broker** N-1 verb-surface\n//! gate: the steady state after every routine brain-only update is a NEW brain\n//! talking to an OLDER broker (the broker almost never updates), so the current\n//! brain must come up + serve against a prior-era broker over the socket verbs\n//! without a clap/arity rejection or a verb desync (KH-2.3 — \"the single most\n//! update-frequency-sensitive invariant\").\n//!\n//! This GROWS the single-binary argv scaffold (`spt-daemon` `n1_compat.rs`, which\n//! only proves the current brain *parses* both argv shapes) into a real\n//! **cross-binary** pairing: a pinned prior `spt` binary is the broker, the\n//! current-tree `spt daemon brain` is the brain.\n//!\n//! ## The pin (doyle D7 vet, call 4a)\n//!\n//! The pin is **NOT a pre-restoration release** — a pre-split binary has no brain\n//! supervisor (that absence *is* the regression), so it cannot pair at all. The\n//! pin is the **restoration split boundary: D1 skeleton `0c95435`** (the first\n//! commit where the broker spawns a brain child). D1 also gives the **widest** N-1\n//! window: its broker spawns its own brain with the *bare* pre-D3-2 argv (no\n//! `--generation` / `--start-reason`), and the current brain accepts both shapes.\n//! **Advance rule:** the pin advances to the latest PUBLISHED split-era release\n//! once one exists — so post-v0.4.0 this gate automatically tests the real fleet\n//! N-1, not a frozen D1 ref.\n//!\n//! ## Pairing mechanics (doyle call 4b — SOCKET VERBS, not supervision custody)\n//!\n//! An old broker's supervisor execs its OWN `current_exe`; it will never\n//! spontaneously spawn the new brain, and bending the D6 record-driven selection\n//! into a test hack would test the wrong thing. So: start the old `spt daemon run`\n//! (a real process — it spawns its own old brain, which is harmless idle noise),\n//! then **test-launch the current-tree `spt daemon brain` against the old broker's\n//! socket/home** and prove it serves the verb surface. The compat surface is the\n//! socket verbs + argv, not who supervises whom.\n//!\n//! ## How to run\n//!\n//! Gated on `SPT_N1_OLD_BROKER` = an absolute path to the pinned-ref `spt` binary\n//! (CI builds it from `0c95435`). Absent → the test SKIPS (like the two-host\n//! gate), so a bare `cargo test` is green without the old binary. CI sets it on\n//! both runners and the skip becomes a real gate.\n// [int->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n// NOTE: this gate also exercises REQ-HAZARD-HANDOFF-ARGV-COMPAT int-grade\n// (cross-binary new-brain × old-broker), but per D7's one-activation discipline\n// (doyle call 4) it is NOT tagged [int->REQ-HAZARD-HANDOFF-ARGV-COMPAT] — that\n// REQ stays [impl,unit]; formalizing its int stage is a later activation commit.\n\nuse std::io::Read;\nuse std::path::PathBuf;\nuse std::process::{Child, Command, Stdio};\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nmod common;\nuse common::CommandNoWindowExt;\n\n/// Best-effort cross-platform kill (mirrors the other daemon E2E helpers).\nfn kill(child: &mut Child) {\n    #[cfg(windows)]\n    {\n        let _ = Command::new(\"taskkill\")\n            .no_window()\n            .args([\"/PID\", &child.id().to_string(), \"/F\", \"/T\"])\n            .output();\n    }\n    let _ = child.kill();\n    let _ = child.wait();\n}\n\n#[test]\nfn new_brain_serves_against_old_broker() {\n    // Gate: the pinned old-broker binary (CI builds it from 0c95435). Absent =\n    // skip, exactly like the two-host rig gate — a bare `cargo test` stays green.\n    let old_broker = match std::env::var(\"SPT_N1_OLD_BROKER\") {\n        Ok(p) if !p.is_empty() => PathBuf::from(p),\n        _ => {\n            eprintln!(\n                \"SKIP n1_pairing: set SPT_N1_OLD_BROKER to a pinned-ref `spt` binary \\\n                 (the restoration split boundary, 0c95435) to run the new-brain × old-broker gate\"\n            );\n            return;\n        }\n    };\n    assert!(\n        old_broker.is_file(),\n        \"SPT_N1_OLD_BROKER does not point at a file: {}\",\n        old_broker.display()\n    );\n\n    let current = PathBuf::from(env!(\"CARGO_BIN_EXE_spt\"));\n    // Single-test binary by design — owns the process-global SPT_HOME on its sole\n    // thread. A 2nd #[test] here must bring the env-lock discipline (D6 lesson).\n    let home = tempfile::tempdir().expect(\"tempdir\");\n    std::env::set_var(\"SPT_HOME\", home.path());\n    let ready = home.path().join(\"brain.ready\");\n\n    // ── Start the OLD broker (`<pin> daemon run`); it binds the broker socket and\n    //    spawns its OWN old brain, which writes brain.ready once it has connected\n    //    — our signal that the old broker is serving. ──\n    let mut old = Command::new(&old_broker)\n        .no_window()\n        .args([\"daemon\", \"run\"])\n        .env(\"SPT_HOME\", home.path())\n        .stdout(Stdio::null())\n        .stderr(Stdio::null())\n        .spawn()\n        .expect(\"spawn old `daemon run`\");\n\n    let up_deadline = Instant::now() + Duration::from_secs(30);\n    while Instant::now() < up_deadline && !ready.exists() {\n        thread::sleep(Duration::from_millis(50));\n    }\n    if !ready.exists() {\n        kill(&mut old);\n        panic!(\"old broker never brought its brain up (no brain.ready) — cannot pair\");\n    }\n\n    // ── Test-launch the CURRENT brain against the OLD broker's socket/home, with\n    //    the stamped (post-D3-2) argv the old broker never emits — the new-brain ×\n    //    old-broker direction. Capture stderr to read its handshake trace. ──\n    let stderr_path = home.path().join(\"current-brain.stderr\");\n    let stderr_file = std::fs::File::create(&stderr_path).expect(\"create stderr capture\");\n    let mut brain = Command::new(&current)\n        .no_window()\n        .args([\n            \"daemon\",\n            \"brain\",\n            \"--generation\",\n            \"99\",\n            \"--start-reason\",\n            \"update\",\n        ])\n        .env(\"SPT_HOME\", home.path())\n        .stdout(Stdio::null())\n        .stderr(stderr_file)\n        .spawn()\n        .expect(\"spawn current `daemon brain`\");\n\n    // Let it connect + handshake + run several heartbeats against the old broker.\n    thread::sleep(Duration::from_secs(6));\n\n    // (1) No clap/arity reject, no verb desync: the current brain is STILL ALIVE\n    //     (a parse failure exits at once with code 2; a verb desync would drop the\n    //     broker connection and exit for respawn).\n    let alive = matches!(brain.try_wait(), Ok(None));\n\n    kill(&mut brain);\n    kill(&mut old);\n\n    let trace = {\n        let mut s = String::new();\n        let _ = std::fs::File::open(&stderr_path).and_then(|mut f| f.read_to_string(&mut s));\n        // W3 REQ-DAEMON-STDERR-PERSIST redirects the brain's OWN stderr FD to\n        // SPT_HOME/logs/daemon.stderr.log as run_brain's first act, so BRAIN_UP and\n        // the steady-state trace land there, not on the inherited handle above.\n        // Read both so the N-1 assertions see the brain's diagnostics wherever they went.\n        let persist = home.path().join(\"logs\").join(\"daemon.stderr.log\");\n        let _ = std::fs::File::open(&persist).and_then(|mut f| f.read_to_string(&mut s));\n        s\n    };\n\n    assert!(\n        alive,\n        \"the current brain must stay alive against the old broker (no clap/arity reject, \\\n         no verb desync) — new-brain × old-broker N-1 steady state.\\nTRACE:\\n{trace}\"\n    );\n    // (2) The handshake completed: it connected to the old broker's socket.\n    assert!(\n        trace.contains(\"BRAIN_UP\"),\n        \"the current brain must connect + handshake the old broker (BRAIN_UP).\\nTRACE:\\n{trace}\"\n    );\n    // (3) Stable heartbeats: the net-status verb round-trips against the old\n    //     broker every heartbeat — a broker loss would print BRAIN_BROKER_LOST.\n    assert!(\n        !trace.contains(\"BRAIN_BROKER_LOST\"),\n        \"the current brain lost the old broker mid-run — a verb-surface desync.\\nTRACE:\\n{trace}\"\n    );\n}\n"}],"details":{"displayContent":{"text":"//! Restoration D7-2 (ADR-0018 V6) — **new-brain × old-broker** N-1 verb-surface\r\n//! gate: the steady state after every routine brain-only update is a NEW brain\r\n//! talking to an OLDER broker (the broker almost never updates), so the current\r\n//! brain must come up + serve against a prior-era broker over the socket verbs\r\n//! without a clap/arity rejection or a verb desync (KH-2.3 — \"the single most\r\n//! update-frequency-sensitive invariant\").\r\n//!\r\n//! This GROWS the single-binary argv scaffold (`spt-daemon` `n1_compat.rs`, which\r\n//! only proves the current brain *parses* both argv shapes) into a real\r\n//! **cross-binary** pairing: a pinned prior `spt` binary is the broker, the\r\n//! current-tree `spt daemon brain` is the brain.\r\n//!\r\n//! ## The pin (doyle D7 vet, call 4a)\r\n//!\r\n//! The pin is **NOT a pre-restoration release** — a pre-split binary has no brain\r\n//! supervisor (that absence *is* the regression), so it cannot pair at all. The\r\n//! pin is the **restoration split boundary: D1 skeleton `0c95435`** (the first\r\n//! commit where the broker spawns a brain child). D1 also gives the **widest** N-1\r\n//! window: its broker spawns its own brain with the *bare* pre-D3-2 argv (no\r\n//! `--generation` / `--start-reason`), and the current brain accepts both shapes.\r\n//! **Advance rule:** the pin advances to the latest PUBLISHED split-era release\r\n//! once one exists — so post-v0.4.0 this gate automatically tests the real fleet\r\n//! N-1, not a frozen D1 ref.\r\n//!\r\n//! ## Pairing mechanics (doyle call 4b — SOCKET VERBS, not supervision custody)\r\n//!\r\n//! An old broker's supervisor execs its OWN `current_exe`; it will never\r\n//! spontaneously spawn the new brain, and bending the D6 record-driven selection\r\n//! into a test hack would test the wrong thing. So: start the old `spt daemon run`\r\n//! (a real process — it spawns its own old brain, which is harmless idle noise),\r\n//! then **test-launch the current-tree `spt daemon brain` against the old broker's\r\n//! socket/home** and prove it serves the verb surface. The compat surface is the\r\n//! socket verbs + argv, not who supervises whom.\r\n//!\r\n//! ## How to run\r\n//!\r\n//! Gated on `SPT_N1_OLD_BROKER` = an absolute path to the pinned-ref `spt` binary\r\n//! (CI builds it from `0c95435`). Absent → the test SKIPS (like the two-host\r\n//! gate), so a bare `cargo test` is green without the old binary. CI sets it on\r\n//! both runners and the skip becomes a real gate.\r\n// [int->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\r\n// NOTE: this gate also exercises REQ-HAZARD-HANDOFF-ARGV-COMPAT int-grade\r\n// (cross-binary new-brain × old-broker), but per D7's one-activation discipline\r\n// (doyle call 4) it is NOT tagged [int->REQ-HAZARD-HANDOFF-ARGV-COMPAT] — that\r\n// REQ stays [impl,unit]; formalizing its int stage is a later activation commit.\r\n\r\nuse std::io::Read;\r\nuse std::path::PathBuf;\r\nuse std::process::{Child, Command, Stdio};\r\nuse std::thread;\r\nuse std::time::{Duration, Instant};\r\n\r\nmod common;\r\nuse common::CommandNoWindowExt;\r\n\r\n/// Best-effort cross-platform kill (mirrors the other daemon E2E helpers).\r\nfn kill(child: &mut Child) {\r\n    #[cfg(windows)]\r\n    {\r\n        let _ = Command::new(\"taskkill\")\r\n            .no_window()\r\n            .args([\"/PID\", &child.id().to_string(), \"/F\", \"/T\"])\r\n            .output();\r\n    }\r\n    let _ = child.kill();\r\n    let _ = child.wait();\r\n}\r\n\r\n#[test]\r\nfn new_brain_serves_against_old_broker() {\r\n    // Gate: the pinned old-broker binary (CI builds it from 0c95435). Absent =\r\n    // skip, exactly like the two-host rig gate — a bare `cargo test` stays green.\r\n    let old_broker = match std::env::var(\"SPT_N1_OLD_BROKER\") {\r\n        Ok(p) if !p.is_empty() => PathBuf::from(p),\r\n        _ => {\r\n            eprintln!(\r\n                \"SKIP n1_pairing: set SPT_N1_OLD_BROKER to a pinned-ref `spt` binary \\\r\n                 (the restoration split boundary, 0c95435) to run the new-brain × old-broker gate\"\r\n            );\r\n            return;\r\n        }\r\n    };\r\n    assert!(\r\n        old_broker.is_file(),\r\n        \"SPT_N1_OLD_BROKER does not point at a file: {}\",\r\n        old_broker.display()\r\n    );\r\n\r\n    let current = PathBuf::from(env!(\"CARGO_BIN_EXE_spt\"));\r\n    // Single-test binary by design — owns the process-global SPT_HOME on its sole\r\n    // thread. A 2nd #[test] here must bring the env-lock discipline (D6 lesson).\r\n    let home = tempfile::tempdir().expect(\"tempdir\");\r\n    std::env::set_var(\"SPT_HOME\", home.path());\r\n    let ready = home.path().join(\"brain.ready\");\r\n\r\n    // ── Start the OLD broker (`<pin> daemon run`); it binds the broker socket and\r\n    //    spawns its OWN old brain, which writes brain.ready once it has connected\r\n    //    — our signal that the old broker is serving. ──\r\n    let mut old = Command::new(&old_broker)\r\n        .no_window()\r\n        .args([\"daemon\", \"run\"])\r\n        .env(\"SPT_HOME\", home.path())\r\n        .stdout(Stdio::null())\r\n        .stderr(Stdio::null())\r\n        .spawn()\r\n        .expect(\"spawn old `daemon run`\");\r\n\r\n    let up_deadline = Instant::now() + Duration::from_secs(30);\r\n    while Instant::now() < up_deadline && !ready.exists() {\r\n        thread::sleep(Duration::from_millis(50));\r\n    }\r\n    if !ready.exists() {\r\n        kill(&mut old);\r\n        panic!(\"old broker never brought its brain up (no brain.ready) — cannot pair\");\r\n    }\r\n\r\n    // ── Test-launch the CURRENT brain against the OLD broker's socket/home, with\r\n    //    the stamped (post-D3-2) argv the old broker never emits — the new-brain ×\r\n    //    old-broker direction. Capture stderr to read its handshake trace. ──\r\n    let stderr_path = home.path().join(\"current-brain.stderr\");\r\n    let stderr_file = std::fs::File::create(&stderr_path).expect(\"create stderr capture\");\r\n    let mut brain = Command::new(&current)\r\n        .no_window()\r\n        .args([\r\n            \"daemon\",\r\n            \"brain\",\r\n            \"--generation\",\r\n            \"99\",\r\n            \"--start-reason\",\r\n            \"update\",\r\n        ])\r\n        .env(\"SPT_HOME\", home.path())\r\n        .stdout(Stdio::null())\r\n        .stderr(stderr_file)\r\n        .spawn()\r\n        .expect(\"spawn current `daemon brain`\");\r\n\r\n    // Let it connect + handshake + run several heartbeats against the old broker.\r\n    thread::sleep(Duration::from_secs(6));\r\n\r\n    // (1) No clap/arity reject, no verb desync: the current brain is STILL ALIVE\r\n    //     (a parse failure exits at once with code 2; a verb desync would drop the\r\n    //     broker connection and exit for respawn).\r\n    let alive = matches!(brain.try_wait(), Ok(None));\r\n\r\n    kill(&mut brain);\r\n    kill(&mut old);\r\n\r\n    let trace = {\r\n        let mut s = String::new();\r\n        let _ = std::fs::File::open(&stderr_path).and_then(|mut f| f.read_to_string(&mut s));\r\n        // W3 REQ-DAEMON-STDERR-PERSIST redirects the brain's OWN stderr FD to\r\n        // SPT_HOME/logs/daemon.stderr.log as run_brain's first act, so BRAIN_UP and\r\n        // the steady-state trace land there, not on the inherited handle above.\r\n        // Read both so the N-1 assertions see the brain's diagnostics wherever they went.\r\n        let persist = home.path().join(\"logs\").join(\"daemon.stderr.log\");\r\n        let _ = std::fs::File::open(&persist).and_then(|mut f| f.read_to_string(&mut s));\r\n        s\r\n    };\r\n\r\n    assert!(\r\n        alive,\r\n        \"the current brain must stay alive against the old broker (no clap/arity reject, \\\r\n         no verb desync) — new-brain × old-broker N-1 steady state.\\nTRACE:\\n{trace}\"\r\n    );\r\n    // (2) The handshake completed: it connected to the old broker's socket.\r\n    assert!(\r\n        trace.contains(\"BRAIN_UP\"),\r\n        \"the current brain must connect + handshake the old broker (BRAIN_UP).\\nTRACE:\\n{trace}\"\r\n    );\r\n    // (3) Stable heartbeats: the net-status verb round-trips against the old\r\n    //     broker every heartbeat — a broker loss would print BRAIN_BROKER_LOST.\r\n    assert!(\r\n        !trace.contains(\"BRAIN_BROKER_LOST\"),\r\n        \"the current brain lost the old broker mid-run — a verb-surface desync.\\nTRACE:\\n{trace}\"\r\n    );\r\n}\r\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\tests\\n1_pairing.rs"}}},"isError":false,"timestamp":1784247426440}}
{"type":"message","id":"e16d5e58","parentId":"c08d50c7","timestamp":"2026-07-17T00:17:06.492Z","message":{"role":"toolResult","toolCallId":"call_N5Ftou6pFssyuaSbMvU0n4Wy|fc_099a7567dadef391016a597482627c819ba4874bb3202dacc6","toolName":"read","content":[{"type":"text","text":"## [0.33.0] - 2026-07-16\n…\n### Changed\n…\n- **Breaking: `spt whoami` reports only your own identity.** It now prints the\n…\n- **Listing endpoints is now fast.** `spt endpoint list` (both the human table\n…\n\n### Added\n\n- `spt daemon status` now reports the health of the project index that powers the\n  fast listings — when it was last generated, how many projects it covers, the\n  last error if any, and repair and stale-read counters. The `--json` output\n  carries the same detail in a `project_index` block. The index file itself lives\n  at `$SPT_HOME/index/project-index.json` and is derived state: safe to delete,\n  and rebuilt by the daemon. A present index file is not by itself proof of\n  health — the status report is.\n\n### Fixed\n\n- The install instructions now name the real release files —\n  `spt-x86_64-windows.exe`, `spt-x86_64-linux`, and `spt-x86_64-linux-musl` —\n  with a per-platform download example and the `chmod +x` step on Linux. The\n  earlier instructions referenced file names that were never published.\n\n- The `[update.post]` adapter hook is now fully documented: it runs once when an\n  adapter is first added, runs in the foreground under a 120-second bound,\n  surfaces its own failures, and is verified before you are notified. This\n  matters when installing a fresh adapter such as the Claude Code plugin.\n\n## [0.32.0] - 2026-07-15\n\nA distribution and documentation release. Releases now come from a private\nchannel through the GitHub CLI, every node serves its own copy of the docs on\nlocalhost, and a single `spt update` brings the whole node — core and adapters —\ncurrent in one command.\n\n> **Starting the docs server needs a daemon restart.** The docs server and\n> `spt daemon refresh` live in the always-on daemon. `spt update --restart`\n> brings them up via a full daemon restart (your live sessions restart as the\n> daemon comes back). A bare `spt update` swaps the core binary in place and\n> leaves the running daemon untouched, so the new docs server starts on the\n> daemon's next restart.\n\n### Added\n\n- `spt install` — self-install this binary onto the node. Run it once from a\n  freshly downloaded release binary: it places itself at the canonical install\n  location, adds that location to your PATH, and refuses a binary built for a\n  different platform. Non-interactive and safe to re-run. `--dir <path>` chooses\n  the install directory; `--no-path` skips the PATH change. This is the\n  bootstrap path for a brand-new node.\n\n- Node-local documentation, served by the daemon on `http://localhost:5474`.\n  - `spt docs` opens the docs in your browser.\n  - `spt docs url` prints the resolved URL (honoring any port override).\n  The port can be changed with `docs_port` in `daemon.json` or the\n  `SPT_DOCS_PORT` environment variable; the server listens on loopback only.\n  The documentation that used to live at a public web address is now read here,\n  on your own node, always matching your installed version.\n\n- Every release now ships a documentation bundle (`spt-docs.tar.gz`) as a signed\n  release asset. `spt update` downloads it and lands it at `$SPT_HOME/docs`, so\n  the docs your node serves always match the binary you are running. A\n  docs-download problem never blocks a binary update — it is reported and\n  retried on the next fetch.\n\n- `spt update adapters [<name>[,<name>…]]` — update your release-shipped\n  adapters. With no names it updates them all; with a comma-separated list it\n  updates just those (names are validated up front, so a typo updates nothing).\n  Each adapter reports its own result, and one failing adapter does not stop the\n  rest. This is a shorter alias for `spt adapter update`, which still works and\n  now also accepts a comma-separated list.\n\n- `spt update --restart` — the one-step full cycle: fetch, update adapters, then\n  restart the whole daemon onto the new version. Use it when you want\n  everything — core, adapters, and the always-on daemon — brought current in a\n  single command. Your live sessions restart and come back on their own.\n\n- `spt daemon refresh` — restart just the daemon's coordinator in place, with no\n  binary change and without stopping the daemon. Hosted terminals and the\n  network layer keep running. This is the recovery verb for a stuck coordinator\n  that previously needed a full `daemon stop`/`start`, which killed every hosted\n  session.\n\n### Changed\n\n- Bare `spt update` now brings the whole node current: it applies a staged core\n  update (if any) and then updates your adapters, in that order. When the core\n  is already current, only the adapters update. Pass `-c` / `--core-only` to\n  update the core binary alone and skip the adapters step.\n\n- Self-update now fetches releases through the GitHub CLI (`gh`) from a private\n  release channel instead of a public web address. `gh` is now a prerequisite\n  for `spt update fetch` and for the `spt install` bootstrap. If `gh` is missing\n  or not signed in, the update stops with a clear message — how to install `gh`\n  for your operating system (`winget` / `brew` / your package manager) and to\n…\n  token.\n\n…\n\n\n[Showing lines 84-173 of 1126. Use :174 to continue]"}],"details":{"truncation":{"content":"\r\n### Added\r\n\r\n- `spt daemon status` now reports the health of the project index that powers the\r\n  fast listings — when it was last generated, how many projects it covers, the\r\n  last error if any, and repair and stale-read counters. The `--json` output\r\n  carries the same detail in a `project_index` block. The index file itself lives\r\n  at `$SPT_HOME/index/project-index.json` and is derived state: safe to delete,\r\n  and rebuilt by the daemon. A present index file is not by itself proof of\r\n  health — the status report is.\r\n\r\n### Fixed\r\n\r\n- The install instructions now name the real release files —\r\n  `spt-x86_64-windows.exe`, `spt-x86_64-linux`, and `spt-x86_64-linux-musl` —\r\n  with a per-platform download example and the `chmod +x` step on Linux. The\r\n  earlier instructions referenced file names that were never published.\r\n\r\n- The `[update.post]` adapter hook is now fully documented: it runs once when an\r\n  adapter is first added, runs in the foreground under a 120-second bound,\r\n  surfaces its own failures, and is verified before you are notified. This\r\n  matters when installing a fresh adapter such as the Claude Code plugin.\r\n\r\n## [0.32.0] - 2026-07-15\r\n\r\nA distribution and documentation release. Releases now come from a private\r\nchannel through the GitHub CLI, every node serves its own copy of the docs on\r\nlocalhost, and a single `spt update` brings the whole node — core and adapters —\r\ncurrent in one command.\r\n\r\n> **Starting the docs server needs a daemon restart.** The docs server and\r\n> `spt daemon refresh` live in the always-on daemon. `spt update --restart`\r\n> brings them up via a full daemon restart (your live sessions restart as the\r\n> daemon comes back). A bare `spt update` swaps the core binary in place and\r\n> leaves the running daemon untouched, so the new docs server starts on the\r\n> daemon's next restart.\r\n\r\n### Added\r\n\r\n- `spt install` — self-install this binary onto the node. Run it once from a\r\n  freshly downloaded release binary: it places itself at the canonical install\r\n  location, adds that location to your PATH, and refuses a binary built for a\r\n  different platform. Non-interactive and safe to re-run. `--dir <path>` chooses\r\n  the install directory; `--no-path` skips the PATH change. This is the\r\n  bootstrap path for a brand-new node.\r\n\r\n- Node-local documentation, served by the daemon on `http://localhost:5474`.\r\n  - `spt docs` opens the docs in your browser.\r\n  - `spt docs url` prints the resolved URL (honoring any port override).\r\n  The port can be changed with `docs_port` in `daemon.json` or the\r\n  `SPT_DOCS_PORT` environment variable; the server listens on loopback only.\r\n  The documentation that used to live at a public web address is now read here,\r\n  on your own node, always matching your installed version.\r\n\r\n- Every release now ships a documentation bundle (`spt-docs.tar.gz`) as a signed\r\n  release asset. `spt update` downloads it and lands it at `$SPT_HOME/docs`, so\r\n  the docs your node serves always match the binary you are running. A\r\n  docs-download problem never blocks a binary update — it is reported and\r\n  retried on the next fetch.\r\n\r\n- `spt update adapters [<name>[,<name>…]]` — update your release-shipped\r\n  adapters. With no names it updates them all; with a comma-separated list it\r\n  updates just those (names are validated up front, so a typo updates nothing).\r\n  Each adapter reports its own result, and one failing adapter does not stop the\r\n  rest. This is a shorter alias for `spt adapter update`, which still works and\r\n  now also accepts a comma-separated list.\r\n\r\n- `spt update --restart` — the one-step full cycle: fetch, update adapters, then\r\n  restart the whole daemon onto the new version. Use it when you want\r\n  everything — core, adapters, and the always-on daemon — brought current in a\r\n  single command. Your live sessions restart and come back on their own.\r\n\r\n- `spt daemon refresh` — restart just the daemon's coordinator in place, with no\r\n  binary change and without stopping the daemon. Hosted terminals and the\r\n  network layer keep running. This is the recovery verb for a stuck coordinator\r\n  that previously needed a full `daemon stop`/`start`, which killed every hosted\r\n  session.\r\n\r\n### Changed\r\n\r\n- Bare `spt update` now brings the whole node current: it applies a staged core\r\n  update (if any) and then updates your adapters, in that order. When the core\r\n  is already current, only the adapters update. Pass `-c` / `--core-only` to\r\n  update the core binary alone and skip the adapters step.\r\n\r\n- Self-update now fetches releases through the GitHub CLI (`gh`) from a private\r\n  release channel instead of a public web address. `gh` is now a prerequisite\r\n  for `spt update fetch` and for the `spt install` bootstrap. If `gh` is missing\r\n  or not signed in, the update stops with a clear message — how to install `gh`\r\n  for your operating system (`winget` / `brew` / your package manager) and to\r","truncated":true,"truncatedBy":"lines","totalLines":1043,"totalBytes":4970,"outputLines":90,"outputBytes":4970,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"## [0.33.0] - 2026-07-16\n…\n### Changed\n…\n- **Breaking: `spt whoami` reports only your own identity.** It now prints the\n…\n- **Listing endpoints is now fast.** `spt endpoint list` (both the human table\n…\n\r\n### Added\r\n\r\n- `spt daemon status` now reports the health of the project index that powers the\r\n  fast listings — when it was last generated, how many projects it covers, the\r\n  last error if any, and repair and stale-read counters. The `--json` output\r\n  carries the same detail in a `project_index` block. The index file itself lives\r\n  at `$SPT_HOME/index/project-index.json` and is derived state: safe to delete,\r\n  and rebuilt by the daemon. A present index file is not by itself proof of\r\n  health — the status report is.\r\n\r\n### Fixed\r\n\r\n- The install instructions now name the real release files —\r\n  `spt-x86_64-windows.exe`, `spt-x86_64-linux`, and `spt-x86_64-linux-musl` —\r\n  with a per-platform download example and the `chmod +x` step on Linux. The\r\n  earlier instructions referenced file names that were never published.\r\n\r\n- The `[update.post]` adapter hook is now fully documented: it runs once when an\r\n  adapter is first added, runs in the foreground under a 120-second bound,\r\n  surfaces its own failures, and is verified before you are notified. This\r\n  matters when installing a fresh adapter such as the Claude Code plugin.\r\n\r\n## [0.32.0] - 2026-07-15\r\n\r\nA distribution and documentation release. Releases now come from a private\r\nchannel through the GitHub CLI, every node serves its own copy of the docs on\r\nlocalhost, and a single `spt update` brings the whole node — core and adapters —\r\ncurrent in one command.\r\n\r\n> **Starting the docs server needs a daemon restart.** The docs server and\r\n> `spt daemon refresh` live in the always-on daemon. `spt update --restart`\r\n> brings them up via a full daemon restart (your live sessions restart as the\r\n> daemon comes back). A bare `spt update` swaps the core binary in place and\r\n> leaves the running daemon untouched, so the new docs server starts on the\r\n> daemon's next restart.\r\n\r\n### Added\r\n\r\n- `spt install` — self-install this binary onto the node. Run it once from a\r\n  freshly downloaded release binary: it places itself at the canonical install\r\n  location, adds that location to your PATH, and refuses a binary built for a\r\n  different platform. Non-interactive and safe to re-run. `--dir <path>` chooses\r\n  the install directory; `--no-path` skips the PATH change. This is the\r\n  bootstrap path for a brand-new node.\r\n\r\n- Node-local documentation, served by the daemon on `http://localhost:5474`.\r\n  - `spt docs` opens the docs in your browser.\r\n  - `spt docs url` prints the resolved URL (honoring any port override).\r\n  The port can be changed with `docs_port` in `daemon.json` or the\r\n  `SPT_DOCS_PORT` environment variable; the server listens on loopback only.\r\n  The documentation that used to live at a public web address is now read here,\r\n  on your own node, always matching your installed version.\r\n\r\n- Every release now ships a documentation bundle (`spt-docs.tar.gz`) as a signed\r\n  release asset. `spt update` downloads it and lands it at `$SPT_HOME/docs`, so\r\n  the docs your node serves always match the binary you are running. A\r\n  docs-download problem never blocks a binary update — it is reported and\r\n  retried on the next fetch.\r\n\r\n- `spt update adapters [<name>[,<name>…]]` — update your release-shipped\r\n  adapters. With no names it updates them all; with a comma-separated list it\r\n  updates just those (names are validated up front, so a typo updates nothing).\r\n  Each adapter reports its own result, and one failing adapter does not stop the\r\n  rest. This is a shorter alias for `spt adapter update`, which still works and\r\n  now also accepts a comma-separated list.\r\n\r\n- `spt update --restart` — the one-step full cycle: fetch, update adapters, then\r\n  restart the whole daemon onto the new version. Use it when you want\r\n  everything — core, adapters, and the always-on daemon — brought current in a\r\n  single command. Your live sessions restart and come back on their own.\r\n\r\n- `spt daemon refresh` — restart just the daemon's coordinator in place, with no\r\n  binary change and without stopping the daemon. Hosted terminals and the\r\n  network layer keep running. This is the recovery verb for a stuck coordinator\r\n  that previously needed a full `daemon stop`/`start`, which killed every hosted\r\n  session.\r\n\r\n### Changed\r\n\r\n- Bare `spt update` now brings the whole node current: it applies a staged core\r\n  update (if any) and then updates your adapters, in that order. When the core\r\n  is already current, only the adapters update. Pass `-c` / `--core-only` to\r\n  update the core binary alone and skip the adapters step.\r\n\r\n- Self-update now fetches releases through the GitHub CLI (`gh`) from a private\r\n  release channel instead of a public web address. `gh` is now a prerequisite\r\n  for `spt update fetch` and for the `spt install` bootstrap. If `gh` is missing\r\n  or not signed in, the update stops with a clear message — how to install `gh`\r\n  for your operating system (`winget` / `brew` / your package manager) and to\r\n…\n  token.\n\n…\n","startLine":57,"lineNumbers":[57,null,64,null,66,null,75,null,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,null,175,176,null,181]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\CHANGELOG.md"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1126,"totalBytes":4970,"outputLines":90,"outputBytes":4970,"shownRange":{"start":84,"end":173},"nextOffset":174}}},"isError":false,"timestamp":1784247426491}}
{"type":"message","id":"1b586c0b","parentId":"e16d5e58","timestamp":"2026-07-17T00:17:06.551Z","message":{"role":"toolResult","toolCallId":"call_p3umcPaW57bhHL3xkwxYVkWJ|fc_099a7567dadef391016a5974826290819ba18657f0f32b9891","toolName":"read","content":[{"type":"text","text":"## Networking\n…\n### Subnet notifications\n…\n- **Spool: per-subnet, replicated across that subnet's nodes** (reuses subnet-registry distribution). Each notif is tagged with its subnet; **dismiss-state replicates subnet-wide**, eventually consistent (dismiss on node A clears it on node B). \"Node notif spool\" = each node's local replica. *(Forward seam: dismissal — and the spool key — generalize to **per-(subnet, user)** when the cross-user model lands.)*\n…\n- **Resurface** re-delivers *undismissed* notifs at boundaries, gated to avoid nagging: skip if already **seen on this endpoint**, and skip if surfaced anywhere within a **global suppression timeout** (default ~1h, configurable) — the timeout is cross-endpoint so a notif can't bounce between endpoints. **Resurface boundaries** (reuse existing reported events, no new plumbing): state→active (`wake` from offline/suspended/dormant), `api boundary clear`, `api boundary compact`, and **new-session-start**. The daemon injects undismissed notifs into the activating/cleared/compacted/fresh session's context.\n- **Scope:** an endpoint only ever sees/resurfaces notifs for subnets it is **visible in** (excluded endpoints never receive that subnet's notifs).\n- **Delivery.** Default: a notif to an agent is **just normal SPT messaging to the agent's perch with a notif-flavored envelope** — the existing delivery/inject path handles it and the agent surfaces it conversationally (no new delivery machinery). Optional: a **`notif_command` manifest template** for endpoint-native rendering (OS toast, etc.), substitution keys from the envelope, **combinable** with the agent-surface path (blanket template, not per-notif-type). The `notif_command` seam lives on **both harness-adapter and shell-adapter manifests** — if presence resolves the user not just to an endpoint but to a **Shell attached to it**, the notif renders via *that shell's* `notif_command` (e.g. GameRobot `alert-symbol`). This is the forward generalization an…\n- **Producers (open set):** system events (node-paired, pairing-request, update-available, consent-needed) + agent-issued `spt subnet notify`. New producers register later.\n- **Envelope `from`** — for agent-issued notifs carries the issuer's **endpoint id, node, and subnet** (the subnet is **surfaced to the receiver only if its node is on multiple subnets** — otherwise redundant). Makes notifs attributable now and targetable once the per-(subnet, user) model lands.\n\n<!-- [doc->REQ-NOTIF-2] -->\n**`spt subnet notify` (agent-issued subnet notif; bare `spt notify` moved under the subnet noun at M8-D1)**:\nA command letting any agent **issue a subnet-wide notif** to the user. v1: it **reaches the user on their active endpoint from any agent** (the default broadcast-to-user). *Forward:* gains targeting — **all subnet users (default) or specific subnet users** — once the per-(subnet, user) model lands; the `from` field is what makes targeted/attributed delivery possible.\n\n## Self-update\n\n<!-- [doc->REQ-ADAPTER-UPDATE-MESSAGE] -->\nSeamless, realtime self-updating is a day-one pillar. After a plugin performs the initial spt-core bootstrap, `spt.exe` self-updates from then on and **ripple-updates registered adapters** via each adapter's manifest update declaration. An adapter may include a `[update].message` — a plain human notice (markdown-rendered) that `spt adapter update` prints to stdout only when a new version is **actually applied** (never on a no-op). Useful for post-update operator actions, e.g. `\"Run \\`/reload-plugins\\` in any ongoing sessions.\"` for spt-claude-code.\n\n**release channel (private, gh-carried)** — the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch — refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signing key, and update-set format are unchanged from the public-channel era. <!-- [doc->REQ-UPDATE-GH-TRANSPORT] -->\n\n**docs bundle** — every release ships a platform-independent archive of the **built docs** (HTML + `llms.txt` + `llms-full.txt` + raw markdown + `manifest.schema.json`) as a **signed update-set asset**; apply lands it at `$SPT_HOME/docs`, so a node's docs always match its installed version. A docs-asset failure never fails the binary update (skip loud, retry next fetch). Consumed by the *docs server* (below). <!-- [doc->REQ-DOCS-RELEASE-ASSET] -->\n\n**docs server** — the daemon serves `$SPT_HOME/docs` over HTTP on **loopback only**, default `127.0.0.1:5474` (config/env-overridable). This is the **canonical docs surface** (supersedes the public Pages URL, ADR-0014→ADR-0036): agents and humans on the node read `http://localhost:5474`; `spt docs url` prints the resolved URL, bare `spt docs` opens the browser. The URL-path surface matches the retired Pages site verbatim (`/llms-full.txt`, append-`.md` raw markdown, `/manifest.schema.json`). Never LAN-exposed — a non-loopback bind would re-publish what privating hid. <!-- [doc->REQ-DOCS-LOCAL-SERVER] -->\n_Avoid_: public docs hosting; binding beyond loopback; docs drifting from the installed version.\n\n**update composite (`spt update`)** — the plain verb is the primary form: `update fetch --apply` then `update adapters` (core-first order); with core already current, only adapters update. `--core-only`/`-c` skips adapters; `spt update adapters [<a>[,<b>…]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** — the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch → adapters → `apply --finish` last (the finish restarts the whole daemon, so it is the final act). <!-- [doc->REQ-UPDATE-DEFAULT-COMPOSITE] --> <!-- [doc->REQ-UPDATE-RESTART-SAFE-SWAP] --> <!-- [doc->REQ-UPDATE-…\n\n**daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previously required a full daemon bounce. <!-- [doc->REQ-DAEMON-REFRESH] -->\n\n**project index** — a node's endpoint→project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers — list, picker, endpoint-info, hooks — join index × perch roster and **never run git**; stale renders last-known or `-`, never a stall. Precedence (session-cwd → origin-cwd → context-recency) and rendered names are contract. **Since W3 the cutover is COMPLETE**: `endpoint list` (human + `--json`), …\n\n**index writer duty (daemon)** — the brain hosts ONE writer thread (`projwriter`, spawned beside the live host; single-flight by construction). Batched complexity is contract, `O(P+B+F+C)`: ONE branch enumeration per cycle (`for-each-ref` carries recency + tips), ≤1 tree scan per **changed** `p-*` branch (`ls-tree` at tip, membership cached by tip), ONE derivation per distinct normalized cwd (cache stamped on the repo-identity marker — `.git/config` / the `.git` gitfile — so **ordinary commits are a no-op by construction**); backgrounding the legacy 100+ process loop is REJECTED. Cold start: daemon ready + CLI fast before the background publish. Warm start: persisted index readable immediately; **unchanged generation performs no scan**. Any cycle failure kee…\n_Avoid_: synchronous git in any read path (the poisoned-git int rigs + the `gitrun` census exist to catch a regression — new list-shaped surfaces must join the index, never re-derive); readers falling back to enrichment; treating index presence as health; wall-clock CI gates (counters gate, wall-clock is manual acceptance); a writer-maintained generation counter (nothing maintains it — fingerprints only); mutating `p-*` branches outside the ContextStore funnels without a `projinval` nudge (the sync-pull seam nudges; the periodic reconcile is a backstop, not a license). <!-- [doc->REQ-PROJECT-INDEX-READER-CUTOVER] -->\n\n**delivery** — peer-propagated over P2P, layered on self-fetch, with out-of-band still supported. One node learns of / obtains an update (marketplace drop *or* self-fetch from a release channel), gossips availability across the subnet, and peers pull the new binary over the networking layer — the subnet self-heals to latest. **All binaries are signature-verified before handoff regardless of source** (peer-propagation otherwise lets a compromised node poison the subnet). spt-core has its own release signing key, distinct from any OS-publisher code-signing (the Windows-publisher-trust question is separate).\n\n**handoff invariant** — **no endpoint process terminates or suspends during a self-update.** We cannot assume every endpoint can safely suspend. Satisfied by the broker/brain split: routine updates replace only the daemon brain, which rehydrates from disk state and re-attaches to the broker's held PTY/socket/child handles. Harness-hosted endpoints (topology 1) are safe by construction; spt-hosted endpoints (topology 2) are kept alive by the broker holding their PTY masters + child processes across the brain restart.\n\n…\n\n\n[Showing lines 559-593 of 889. Use :594 to continue. Some lines truncated to 768 chars]"}],"details":{"truncation":{"content":"- **Resurface** re-delivers *undismissed* notifs at boundaries, gated to avoid nagging: skip if already **seen on this endpoint**, and skip if surfaced anywhere within a **global suppression timeout** (default ~1h, configurable) — the timeout is cross-endpoint so a notif can't bounce between endpoints. **Resurface boundaries** (reuse existing reported events, no new plumbing): state→active (`wake` from offline/suspended/dormant), `api boundary clear`, `api boundary compact`, and **new-session-start**. The daemon injects undismissed notifs into the activating/cleared/compacted/fresh session's context.\r\n- **Scope:** an endpoint only ever sees/resurfaces notifs for subnets it is **visible in** (excluded endpoints never receive that subnet's notifs).\r\n- **Delivery.** Default: a notif to an agent is **just normal SPT messaging to the agent's perch with a notif-flavored envelope** — the existing delivery/inject path handles it and the agent surfaces it conversationally (no new delivery machinery). Optional: a **`notif_command` manifest template** for endpoint-native rendering (OS toast, etc.), substitution keys from the envelope, **combinable** with the agent-surface path (blanket template, not per-notif-type). The `notif_command` seam lives on **both harness-adapter and shell-adapter manifests** — if presence resolves the user not just to an endpoint but to a **Shell attached to it**, the notif renders via *that shell's* `notif_command` (e.g. GameRobot `alert-symbol`). This is the forward generalization an…\n- **Producers (open set):** system events (node-paired, pairing-request, update-available, consent-needed) + agent-issued `spt subnet notify`. New producers register later.\r\n- **Envelope `from`** — for agent-issued notifs carries the issuer's **endpoint id, node, and subnet** (the subnet is **surfaced to the receiver only if its node is on multiple subnets** — otherwise redundant). Makes notifs attributable now and targetable once the per-(subnet, user) model lands.\r\n\r\n<!-- [doc->REQ-NOTIF-2] -->\r\n**`spt subnet notify` (agent-issued subnet notif; bare `spt notify` moved under the subnet noun at M8-D1)**:\r\nA command letting any agent **issue a subnet-wide notif** to the user. v1: it **reaches the user on their active endpoint from any agent** (the default broadcast-to-user). *Forward:* gains targeting — **all subnet users (default) or specific subnet users** — once the per-(subnet, user) model lands; the `from` field is what makes targeted/attributed delivery possible.\r\n\r\n## Self-update\r\n\r\n<!-- [doc->REQ-ADAPTER-UPDATE-MESSAGE] -->\r\nSeamless, realtime self-updating is a day-one pillar. After a plugin performs the initial spt-core bootstrap, `spt.exe` self-updates from then on and **ripple-updates registered adapters** via each adapter's manifest update declaration. An adapter may include a `[update].message` — a plain human notice (markdown-rendered) that `spt adapter update` prints to stdout only when a new version is **actually applied** (never on a no-op). Useful for post-update operator actions, e.g. `\"Run \\`/reload-plugins\\` in any ongoing sessions.\"` for spt-claude-code.\r\n\r\n**release channel (private, gh-carried)** — the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch — refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signing key, and update-set format are unchanged from the public-channel era. <!-- [doc->REQ-UPDATE-GH-TRANSPORT] -->\r\n\r\n**docs bundle** — every release ships a platform-independent archive of the **built docs** (HTML + `llms.txt` + `llms-full.txt` + raw markdown + `manifest.schema.json`) as a **signed update-set asset**; apply lands it at `$SPT_HOME/docs`, so a node's docs always match its installed version. A docs-asset failure never fails the binary update (skip loud, retry next fetch). Consumed by the *docs server* (below). <!-- [doc->REQ-DOCS-RELEASE-ASSET] -->\r\n\r\n**docs server** — the daemon serves `$SPT_HOME/docs` over HTTP on **loopback only**, default `127.0.0.1:5474` (config/env-overridable). This is the **canonical docs surface** (supersedes the public Pages URL, ADR-0014→ADR-0036): agents and humans on the node read `http://localhost:5474`; `spt docs url` prints the resolved URL, bare `spt docs` opens the browser. The URL-path surface matches the retired Pages site verbatim (`/llms-full.txt`, append-`.md` raw markdown, `/manifest.schema.json`). Never LAN-exposed — a non-loopback bind would re-publish what privating hid. <!-- [doc->REQ-DOCS-LOCAL-SERVER] -->\r\n_Avoid_: public docs hosting; binding beyond loopback; docs drifting from the installed version.\r\n\r\n**update composite (`spt update`)** — the plain verb is the primary form: `update fetch --apply` then `update adapters` (core-first order); with core already current, only adapters update. `--core-only`/`-c` skips adapters; `spt update adapters [<a>[,<b>…]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** — the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch → adapters → `apply --finish` last (the finish restarts the whole daemon, so it is the final act). <!-- [doc->REQ-UPDATE-DEFAULT-COMPOSITE] --> <!-- [doc->REQ-UPDATE-RESTART-SAFE-SWAP] --> <!-- [doc->REQ-UPDATE-…\n\r\n**daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previously required a full daemon bounce. <!-- [doc->REQ-DAEMON-REFRESH] -->\r\n\r\n**project index** — a node's endpoint→project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers — list, picker, endpoint-info, hooks — join index × perch roster and **never run git**; stale renders last-known or `-`, never a stall. Precedence (session-cwd → origin-cwd → context-recency) and rendered names are contract. **Since W3 the cutover is COMPLETE**: `endpoint list` (human + `--json`), …\n\r\n**index writer duty (daemon)** — the brain hosts ONE writer thread (`projwriter`, spawned beside the live host; single-flight by construction). Batched complexity is contract, `O(P+B+F+C)`: ONE branch enumeration per cycle (`for-each-ref` carries recency + tips), ≤1 tree scan per **changed** `p-*` branch (`ls-tree` at tip, membership cached by tip), ONE derivation per distinct normalized cwd (cache stamped on the repo-identity marker — `.git/config` / the `.git` gitfile — so **ordinary commits are a no-op by construction**); backgrounding the legacy 100+ process loop is REJECTED. Cold start: daemon ready + CLI fast before the background publish. Warm start: persisted index readable immediately; **unchanged generation performs no scan**. Any cycle failure kee…\n_Avoid_: synchronous git in any read path (the poisoned-git int rigs + the `gitrun` census exist to catch a regression — new list-shaped surfaces must join the index, never re-derive); readers falling back to enrichment; treating index presence as health; wall-clock CI gates (counters gate, wall-clock is manual acceptance); a writer-maintained generation counter (nothing maintains it — fingerprints only); mutating `p-*` branches outside the ContextStore funnels without a `projinval` nudge (the sync-pull seam nudges; the periodic reconcile is a backstop, not a license). <!-- [doc->REQ-PROJECT-INDEX-READER-CUTOVER] -->\r\n\r\n**delivery** — peer-propagated over P2P, layered on self-fetch, with out-of-band still supported. One node learns of / obtains an update (marketplace drop *or* self-fetch from a release channel), gossips availability across the subnet, and peers pull the new binary over the networking layer — the subnet self-heals to latest. **All binaries are signature-verified before handoff regardless of source** (peer-propagation otherwise lets a compromised node poison the subnet). spt-core has its own release signing key, distinct from any OS-publisher code-signing (the Windows-publisher-trust question is separate).\r\n\r\n**handoff invariant** — **no endpoint process terminates or suspends during a self-update.** We cannot assume every endpoint can safely suspend. Satisfied by the broker/brain split: routine updates replace only the daemon brain, which rehydrates from disk state and re-attaches to the broker's held PTY/socket/child handles. Harness-hosted endpoints (topology 1) are safe by construction; spt-hosted endpoints (topology 2) are kept alive by the broker holding their PTY masters + child processes across the brain restart.\r\n\r","truncated":true,"truncatedBy":"lines","totalLines":331,"totalBytes":10716,"outputLines":35,"outputBytes":10716,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"## Networking\n…\n### Subnet notifications\n…\n- **Spool: per-subnet, replicated across that subnet's nodes** (reuses subnet-registry distribution). Each notif is tagged with its subnet; **dismiss-state replicates subnet-wide**, eventually consistent (dismiss on node A clears it on node B). \"Node notif spool\" = each node's local replica. *(Forward seam: dismissal — and the spool key — generalize to **per-(subnet, user)** when the cross-user model lands.)*\n…\n- **Resurface** re-delivers *undismissed* notifs at boundaries, gated to avoid nagging: skip if already **seen on this endpoint**, and skip if surfaced anywhere within a **global suppression timeout** (default ~1h, configurable) — the timeout is cross-endpoint so a notif can't bounce between endpoints. **Resurface boundaries** (reuse existing reported events, no new plumbing): state→active (`wake` from offline/suspended/dormant), `api boundary clear`, `api boundary compact`, and **new-session-start**. The daemon injects undismissed notifs into the activating/cleared/compacted/fresh session's context.\r\n- **Scope:** an endpoint only ever sees/resurfaces notifs for subnets it is **visible in** (excluded endpoints never receive that subnet's notifs).\r\n- **Delivery.** Default: a notif to an agent is **just normal SPT messaging to the agent's perch with a notif-flavored envelope** — the existing delivery/inject path handles it and the agent surfaces it conversationally (no new delivery machinery). Optional: a **`notif_command` manifest template** for endpoint-native rendering (OS toast, etc.), substitution keys from the envelope, **combinable** with the agent-surface path (blanket template, not per-notif-type). The `notif_command` seam lives on **both harness-adapter and shell-adapter manifests** — if presence resolves the user not just to an endpoint but to a **Shell attached to it**, the notif renders via *that shell's* `notif_command` (e.g. GameRobot `alert-symbol`). This is the forward generalization an…\n- **Producers (open set):** system events (node-paired, pairing-request, update-available, consent-needed) + agent-issued `spt subnet notify`. New producers register later.\r\n- **Envelope `from`** — for agent-issued notifs carries the issuer's **endpoint id, node, and subnet** (the subnet is **surfaced to the receiver only if its node is on multiple subnets** — otherwise redundant). Makes notifs attributable now and targetable once the per-(subnet, user) model lands.\r\n\r\n<!-- [doc->REQ-NOTIF-2] -->\r\n**`spt subnet notify` (agent-issued subnet notif; bare `spt notify` moved under the subnet noun at M8-D1)**:\r\nA command letting any agent **issue a subnet-wide notif** to the user. v1: it **reaches the user on their active endpoint from any agent** (the default broadcast-to-user). *Forward:* gains targeting — **all subnet users (default) or specific subnet users** — once the per-(subnet, user) model lands; the `from` field is what makes targeted/attributed delivery possible.\r\n\r\n## Self-update\r\n\r\n<!-- [doc->REQ-ADAPTER-UPDATE-MESSAGE] -->\r\nSeamless, realtime self-updating is a day-one pillar. After a plugin performs the initial spt-core bootstrap, `spt.exe` self-updates from then on and **ripple-updates registered adapters** via each adapter's manifest update declaration. An adapter may include a `[update].message` — a plain human notice (markdown-rendered) that `spt adapter update` prints to stdout only when a new version is **actually applied** (never on a no-op). Useful for post-update operator actions, e.g. `\"Run \\`/reload-plugins\\` in any ongoing sessions.\"` for spt-claude-code.\r\n\r\n**release channel (private, gh-carried)** — the release channel is a **private** GitHub repo (`BigscreenVR/spt-bs-releases`, ADR-0036); the **gh CLI is the mandated carrier** for release discovery and asset download (each node authenticates via org membership). A node without an authed `gh` cannot fetch — refused loud with OS-specific install hints, never a silent hang. Signature verification is carrier-independent: bytes are verified after download exactly as before; counter, signing key, and update-set format are unchanged from the public-channel era. <!-- [doc->REQ-UPDATE-GH-TRANSPORT] -->\r\n\r\n**docs bundle** — every release ships a platform-independent archive of the **built docs** (HTML + `llms.txt` + `llms-full.txt` + raw markdown + `manifest.schema.json`) as a **signed update-set asset**; apply lands it at `$SPT_HOME/docs`, so a node's docs always match its installed version. A docs-asset failure never fails the binary update (skip loud, retry next fetch). Consumed by the *docs server* (below). <!-- [doc->REQ-DOCS-RELEASE-ASSET] -->\r\n\r\n**docs server** — the daemon serves `$SPT_HOME/docs` over HTTP on **loopback only**, default `127.0.0.1:5474` (config/env-overridable). This is the **canonical docs surface** (supersedes the public Pages URL, ADR-0014→ADR-0036): agents and humans on the node read `http://localhost:5474`; `spt docs url` prints the resolved URL, bare `spt docs` opens the browser. The URL-path surface matches the retired Pages site verbatim (`/llms-full.txt`, append-`.md` raw markdown, `/manifest.schema.json`). Never LAN-exposed — a non-loopback bind would re-publish what privating hid. <!-- [doc->REQ-DOCS-LOCAL-SERVER] -->\r\n_Avoid_: public docs hosting; binding beyond loopback; docs drifting from the installed version.\r\n\r\n**update composite (`spt update`)** — the plain verb is the primary form: `update fetch --apply` then `update adapters` (core-first order); with core already current, only adapters update. `--core-only`/`-c` skips adapters; `spt update adapters [<a>[,<b>…]]` is the adapters leg alone (alias over `spt adapter update`). The composite's invoker always survives, because a routine apply cycles only the **brain** — the *restart-required* message on broker-side releases is a notice, not a restart. `spt update --restart` is the one-step **full cycle**: fetch → adapters → `apply --finish` last (the finish restarts the whole daemon, so it is the final act). <!-- [doc->REQ-UPDATE-DEFAULT-COMPOSITE] --> <!-- [doc->REQ-UPDATE-RESTART-SAFE-SWAP] --> <!-- [doc->REQ-UPDATE-…\n\r\n**daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previously required a full daemon bounce. <!-- [doc->REQ-DAEMON-REFRESH] -->\r\n\r\n**project index** — a node's endpoint→project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers — list, picker, endpoint-info, hooks — join index × perch roster and **never run git**; stale renders last-known or `-`, never a stall. Precedence (session-cwd → origin-cwd → context-recency) and rendered names are contract. **Since W3 the cutover is COMPLETE**: `endpoint list` (human + `--json`), …\n\r\n**index writer duty (daemon)** — the brain hosts ONE writer thread (`projwriter`, spawned beside the live host; single-flight by construction). Batched complexity is contract, `O(P+B+F+C)`: ONE branch enumeration per cycle (`for-each-ref` carries recency + tips), ≤1 tree scan per **changed** `p-*` branch (`ls-tree` at tip, membership cached by tip), ONE derivation per distinct normalized cwd (cache stamped on the repo-identity marker — `.git/config` / the `.git` gitfile — so **ordinary commits are a no-op by construction**); backgrounding the legacy 100+ process loop is REJECTED. Cold start: daemon ready + CLI fast before the background publish. Warm start: persisted index readable immediately; **unchanged generation performs no scan**. Any cycle failure kee…\n_Avoid_: synchronous git in any read path (the poisoned-git int rigs + the `gitrun` census exist to catch a regression — new list-shaped surfaces must join the index, never re-derive); readers falling back to enrichment; treating index presence as health; wall-clock CI gates (counters gate, wall-clock is manual acceptance); a writer-maintained generation counter (nothing maintains it — fingerprints only); mutating `p-*` branches outside the ContextStore funnels without a `projinval` nudge (the sync-pull seam nudges; the periodic reconcile is a backstop, not a license). <!-- [doc->REQ-PROJECT-INDEX-READER-CUTOVER] -->\r\n\r\n**delivery** — peer-propagated over P2P, layered on self-fetch, with out-of-band still supported. One node learns of / obtains an update (marketplace drop *or* self-fetch from a release channel), gossips availability across the subnet, and peers pull the new binary over the networking layer — the subnet self-heals to latest. **All binaries are signature-verified before handoff regardless of source** (peer-propagation otherwise lets a compromised node poison the subnet). spt-core has its own release signing key, distinct from any OS-publisher code-signing (the Windows-publisher-trust question is separate).\r\n\r\n**handoff invariant** — **no endpoint process terminates or suspends during a self-update.** We cannot assume every endpoint can safely suspend. Satisfied by the broker/brain split: routine updates replace only the daemon brain, which rehydrates from disk state and re-attaches to the broker's held PTY/socket/child handles. Harness-hosted endpoints (topology 1) are safe by construction; spt-hosted endpoints (topology 2) are kept alive by the broker holding their PTY masters + child processes across the brain restart.\r\n\r\n…\n","startLine":509,"lineNumbers":[509,null,551,null,556,null,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,null,605]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\CONTEXT.md"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":889,"totalBytes":10716,"outputLines":35,"outputBytes":10716,"shownRange":{"start":559,"end":593},"nextOffset":594},"limits":{"columnTruncated":{"maxColumn":768}}}},"isError":false,"timestamp":1784247426551}}
{"type":"message","id":"d94a8c29","parentId":"1b586c0b","timestamp":"2026-07-17T00:17:06.558Z","message":{"role":"toolResult","toolCallId":"call_8NXqliczbp4b4txzrlCYRYqY|fc_099a7567dadef391016a5974826280819b8515da398ec8f775","toolName":"grep","content":[{"type":"text","text":"# traceable-reqs.toml\n 1985|title = \"B-2 (REMOTE-TRUTH triage §B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals — the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a tr...\n*1986|required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REMOTE-TRUTH B-2 REDUCED (todlando); int ACTIVATED on the green [twohost] rig run 28772170556 @279df99 (2026-07-06). impl = OutputLog::reap_dead_controller (broker.rs: drop a controller whose _writer.is_finished() in-memory ONLY, no stamp — lock-safe under the KIND_SESSIONS snapshot log lock) + the snapshot-closure call site (recover_log(&log) made mut; reap BEFORE reading controller_by/has_controller so the reply + the existing off-lock converge_p...\n 1987|[[requirements]]\n 1988|id = \"REQ-ADAPTER-FLOOR-ENFORCE\"\n 1989|title = \"F-5 (REMOTE-TRUTH triage §F-5 + doyle rulings 2026-07-05): BOTH adapter acquisition verbs (spt adapter add + spt adapter update) REFUSE when the installed spt-core is BELOW the adapter's declared [adapter].min_spt_core_version floor — with an F-1 operator refusal naming the installed core, the floor, and the next action (update spt-core first). ROOT: the floor was PARSED + required (manifest.rs) but never compared to the running core — dead enforcement; and the [update].version_check knob t...\n...\n 2114|[[requirements]]\n*2115|id = \"REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL\"\n 2116|title = \"W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints — daemon start RE-RUNS previously-online spt-hosted endpoints. ROOT rig-proven: daemon stop+start (the apply notice's OWN instruction) kills every hosted endpoint; they stay OFFLINE after start (no resurrection) though records exist (info.json status + adapter + cwd). SCOPE RULING (doyle): re-run-on-start, marked start-reason=daemon-restart; agents' minds ride psyche re-host as today. Int: endpoint online -> daemon stop ...\n 2117|required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED LIFECYCLE-TRUTH W3 (todlando 2026-07-07) per doyle ruling docs/W3-ENDPOINT-SURVIVAL-DESIGN.md @d257d15. impl = a one-shot resume_restart_orphaned_endpoints pass at spawn_live_host start (BEFORE the phantom-clear reconcile) that re-runs the daemon-restart-orphaned set via the shared launch_ledger_resume (launch_harness_brokered_in, resume from last ledger, is_resume, reason tag \"daemon-restart\") — extracted from resume_woken_endpoint's Resume arm. T...\n 2118|\n...\n 2120|id = \"REQ-UPDATE-ONE-SHOT-FINISH\"\n*2121|title = \"W3 (LIFECYCLE-TRUTH): update apply works daemonless and one command finishes the cycle. ROOT (operator wart): update fetch/apply run ensure_daemon_announced (cli.rs:4386) -> on a stopped box they BOOT THE OLD broker pre-swap, guaranteeing the mixed old-broker/new-brain pair + a manual bounce. FIX: apply works daemonless (swap + record, next start runs new bytes); `update apply --finish` (name subject to docs-token gate) completes the cycle: swap -> brain cycle -> broker restart onto new bytes (r...\n*2122|required_stages = [\"impl\", \"unit\"]  # ACTIVATED LIFECYCLE-TRUTH W3 (todlando 2026-07-07). impl = (a) applyhost::apply_staged_daemonless — the swap+record apply with NO broker connect/brain-restart (shared prepare_apply preamble + swap_and_record helper factored out of apply_staged), returning the new ApplyStagedOutcome::AppliedDaemonless; (b) cmd_update_apply drops ensure_daemon_announced (no more old-broker boot pre-swap): when the daemon is up it hands off in place (apply_staged), when it is down it ...\n 2123|\n 2124|[[requirements]]\n 2125|id = \"REQ-DAEMON-STOP-LIVE-SESSION-WARN\"\n 2126|title = \"W3 (LIFECYCLE-TRUTH, promoted old follow-wave seed): `daemon stop` with live hosted sessions warns + requires --force (or names the sessions it will kill) instead of silently killing them.\"\n*2127|required_stages = [\"impl\", \"unit\"]  # ACTIVATED LIFECYCLE-TRUTH W3 (todlando 2026-07-07). impl = a --force flag on `daemon stop` + cmd_daemon_stop queries the live hosted session set (live_hosted_session_ids via the broker sessions()) and, without --force, REFUSES (exit 3) naming the sessions the stop would kill (with the reassurance they come back on the next start — composes with REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL); --force stops anyway. CLI change -> xtask gen regenerated docs-site/src/cli/referenc...\n 2128|\n 2129|[[requirements]]\n 2130|id = \"REQ-RC-RECONNECT-TRUTH\"\n...\n 2175|id = \"REQ-UPDATE-FINISH-COMMUNE-FLUSH\"\n*2176|title = \"DEFERRED (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07 — mint now, impl a FUTURE milestone): make the update swap LOSSLESS for live hosted endpoints by flushing a final echo-commune per endpoint BEFORE the brain-subtree reap. ROOT (operator-surfaced probing --finish): `update apply --finish` = daemonless swap -> daemon RESTART; the graceful `daemon stop` path (daemon.rs:316-325) raises brain_stop then reaper.reap() KILLS the brain subtree (brain + shellwake watchers + detached Psyches) as o...\n 2177|required_stages = []  # minted DEFERRED (doyle 2026-07-07, operator ruled impl waits until after LIFECYCLE-TRUTH); activate at the future milestone that delivers swap-loss-free updates\n 2178|\n 2179|[[requirements]]\n...\n 2299|id = \"REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK\"\n*2300|title = \"UPDATE-WEDGE round-4 (hertz RCA, root reproduced + source-pinned on Windows 2026-07-09): EVERY write on a physical broker connection rides ONE bounded + cancelable + poison-on-failure framed-write primitive — no writer may hold the connection's serialized send gate across an UNBOUNDED OS write. ROOT: controller_writer held the SharedSend = Arc<Mutex<SendHalf>> guard ACROSS a blocking write_frame; on Windows interprocess 2.4.2 routes the send to WriteFileEx + SleepEx(INFINITE, alertable) with N...\n 2301|required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED at mint, UPDATE-WEDGE round-4 (todlando 2026-07-09) per doyle GO. This ACTIVATES (and replaces, dedup at the v0.30.6 merge) the round-3 deferred SEED of the same id — r4 IS the Option-C class fix that SEED reserved; broker-side ⇒ lands via coordinated daemon restart, not a seamless brain-swap. doc = KNOWN-HAZARDS 7.38. impl = conn.rs BrokerConn (gate+watchdog+poison+abort) + full write-site routing sweep. int = brain_decouple.rs non_draining_contr...\n 2302|\n 2303|# ── MSG-IDENTITY milestone (operator GO 2026-07-10: \"two triages + prominent backlog seeds, especially messaging + endpoint identity\"; F-037 EXCLUDED by operator, covered later) ──\n...\n 2386|id = \"REQ-UPDATE-DEFAULT-COMPOSITE\"\n*2387|title = \"THE-FORKENING W4 (operator-grilled 2026-07-14): plain `spt update` = `update fetch --apply` THEN `update adapters` (core-first doctrine order); when core is already current the core leg no-ops and ONLY adapters update; `--core-only`/`-c` skips the adapters leg. GROUNDING (operator-corrected, code-confirmed): fetch --apply cycles the BRAIN only — broker + PTYs survive (apply_staged applyhost.rs:303; the restart-required text is a NOTICE, cli.rs:4615, not behavior) — so the composite's invokin...\n 2388|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2389|\n 2390|[[requirements]]\n*2391|id = \"REQ-UPDATE-RESTART-SAFE-SWAP\"\n*2392|title = \"THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap — fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act — everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish F...\n 2393|required_stages = [\"doc\", \"impl\", \"unit\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2394|\n 2395|[[requirements]]\n...\n 2438|id = \"REQ-REDISPATCH-FINISHED-RETIRE\"\n*2439|title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bound...\n 2440|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16; int ADDED gate round 1; RULING gate round 2: retirement is ELIGIBILITY-VISIBILITY, never teardown — the \"lifecycle-exclude\" arm of the title, remove is REJECTED: the worker's reply send is fire-and-forget, so remove-on-retire races the still-queued reply inside the broker and resets the never-finished send half = requester torn-EOF/NoReply, proven 30/150 vs 0/150 A/B on kitsubito; physical teardown b...\n 2441|\n 2442|[[requirements]]\n\n# crates/\n\n## spt-daemon/\n\n### src/\n#### applyhost.rs\n 376|/// the swapped bytes run when the daemon is NEXT started (either by the operator,\n*377|/// or by `update apply --finish` which restarts it in the same command, riding\n*378|/// REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL so hosted endpoints come back). The\n 379|/// recorded `AppliedPending` promotes on that fresh brain's `ready`, exactly as\n 380|/// the handoff path's does. Runs the identical verify/classify gate as\n 381|/// [`apply_staged`], so every fail-closed refusal is preserved.\n#### livehost.rs\n 423|/// stays daemon-authoritative, the D9-2 posture the resume leg already keeps).\n*424|// [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n 425|#[allow(clippy::too_many_arguments)] // resume material + the two registry lookups; a struct would obscure the call\n 426|fn launch_ledger_resume(\n 427|    perch: &Path,\n...\n 482|\n*483|/// The daemon-restart re-run decision (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL). A pure\n 484|/// function over a candidate perch's facts, so the full belt matrix is unit-tested\n 485|/// without a broker. Only the daemon-restart-ORPHANED, spt-hosted, DEAD set re-runs:\n 486|/// - `state != live_agent` → Skip (not a live agent).\n...\n 498|///   a live custody process; refuse loudly, the breadcrumb feeds the W4 dup-wake rig).\n*499|// [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n 500|#[derive(Debug, PartialEq, Eq)]\n 501|enum RestartResume {\n 502|    Resume,\n...\n 506|\n*507|// [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n 508|fn restart_resume_gate(\n 509|    state: &str,\n 510|    status: Option<&str>,\n...\n 536|\n*537|/// LIFECYCLE-TRUTH W3 (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL): on daemon start, RE-RUN the\n 538|/// harness of every previously-ONLINE spt-hosted endpoint this daemon (re)start\n 539|/// orphaned — so a `daemon stop`+`start` (the apply notice's OWN instruction) no longer\n 540|/// massacres them (seed #6, the field-accept blocker). Runs ONCE at\n...\n 548|/// hiccup — the safe direction, same posture as [`reconcile_hosted_liveness`]).\n*549|// [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n 550|pub fn resume_restart_orphaned_endpoints(\n 551|    owlery: &Path,\n 552|    registered: &[(AdapterRecord, Manifest)],\n...\n 996|            // LIFECYCLE-TRUTH W3 ONE-SHOT daemon-restart endpoint survival\n*997|            // (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL): re-run the harness of every\n 998|            // previously-ONLINE spt-hosted endpoint this (re)start orphaned, BEFORE the\n 999|            // reconcile loop's phantom-clear offlines the stale-online records (the \"was\n 1000|            // online\" signal). A brain restart with a surviving broker re-runs nothing\n 1001|            // (its sessions are live, not orphaned). Belts inside the gate.\n*1002|            // [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n 1003|            resume_restart_orphaned_endpoints(&owlery, &registered, &adapters_dir);\n 1004|        }\n 1005|        while !stop.load(Ordering::SeqCst) {\n...\n 2095|\n*2096|    // [unit->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL] the daemon-restart re-run GATE + both\n 2097|    // belts, as a pure matrix. Only a previously-ONLINE, spt-hosted (controllable),\n 2098|    // orphaned (no live session), non-relay, DEAD-custody endpoint re-runs; every other\n 2099|    // class is excluded, and a LIVE custody pid refuses (the dup guard, not a silent skip).\n\n### tests/\n#### endpoint_survival.rs\n*1|//! LIFECYCLE-TRUTH W3 (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL) — daemon-restart endpoint\n 2|//! survival, against a REAL broker over the real local IPC.\n 3|//!\n 4|//! ## The bug this gate locks down (seed #6, the field-accept blocker)\n...\n 84|\n*85|// [int->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n 86|#[test]\n 87|fn daemon_restart_reruns_a_previously_online_orphaned_endpoint() {\n 88|    let home = tempfile::TempDir::new().expect(\"temp home\");\n\n## spt/src/\n### cli.rs\n 251|        /// The full-cycle form: fetch, update adapters, then finish by\n*252|        /// restarting the daemon onto the new version (`update apply --finish`)\n 253|        /// as the final step — so the whole node, coordinator and live agents,\n 254|        /// runs the new version when it returns. The restart bounces hosted\n 255|        /// sessions (they come back automatically).\n...\n 1454|            // [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n*1455|            // [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\n 1456|            None => cmd_update_composite(core_only, restart),\n 1457|        },\n 1458|        // [impl->REQ-INSTALL-BOOTSTRAP-VERB]\n...\n 4903|        \"Installed spt-core {ver} on disk. The daemon is offline, so nothing is running the new \\\n*4904|         version yet — run `spt update apply --finish` to bring it up on the new version now (any \\\n 4905|         hosted sessions come back automatically), or `spt daemon start`.\\nChangelog: {RELEASES_URL}\"\n 4906|    )\n 4907|}\n...\n 5043|/// One leg of the bare `spt update` composite (REQ-UPDATE-DEFAULT-COMPOSITE,\n*5044|/// REQ-UPDATE-RESTART-SAFE-SWAP).\n 5045|#[derive(Debug, PartialEq, Eq, Clone, Copy)]\n 5046|enum UpdateLeg {\n 5047|    /// `update fetch --apply` — stage + install the core in one step (the\n...\n 5053|    Adapters,\n*5054|    /// `update apply --finish` — swap + full daemon restart. LETHAL: from an\n 5055|    /// spt-hosted session the invoker's PTY dies here, so this leg is always\n 5056|    /// planned LAST (everything else has completed by then).\n 5057|    Finish,\n...\n 5066|// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n*5067|// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\n 5068|fn plan_update_legs(core_only: bool, restart: bool) -> Vec<UpdateLeg> {\n 5069|    let mut legs = Vec::new();\n 5070|    legs.push(if restart {\n...\n 5088|/// PTY keep running (applyhost::apply_staged) — so no re-run machinery exists.\n*5089|/// With `--restart` (REQ-UPDATE-RESTART-SAFE-SWAP) the plan is fetch → adapters\n*5090|/// → `apply --finish` LAST: the one-step safe full-cycle swap.\n 5091|///\n 5092|/// Failure policy: a CORE-leg failure (fetch/apply refused, gh missing…)\n 5093|/// ABORTS the remaining legs — updating adapters against a channel that just\n...\n 5100|// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n*5101|// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\n 5102|fn cmd_update_composite(core_only: bool, restart: bool) -> i32 {\n 5103|    let mut worst = 0;\n 5104|    for leg in plan_update_legs(core_only, restart) {\n...\n 5224|\n*5225|/// `spt update apply --finish` — swap the bytes daemonless (never leaving an old\n 5226|/// coordinator running new-agent code), then RESTART the daemon onto them so the\n 5227|/// whole node runs the new version in one command. The restart rides\n*5228|/// REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL: `daemon start` re-runs the previously\n 5229|/// online hosted endpoints, so the cycle is not a massacre.\n 5230|// [impl->REQ-UPDATE-ONE-SHOT-FINISH]\n 5231|fn cmd_update_apply_finish(\n...\n 12840|    // ── THE-FORKENING W4: the update-UX cluster (REQ-UPDATE-ADAPTERS-VERB,\n*12841|    // REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-RESTART-SAFE-SWAP,\n 12842|    // REQ-DAEMON-REFRESH). ──\n 12843|\n 12844|    // [unit->REQ-UPDATE-ADAPTERS-VERB] comma-list parsing shared by both verb\n...\n 13015|\n*13016|    // [unit->REQ-UPDATE-RESTART-SAFE-SWAP] lethal-leg-LAST: `--restart` plans\n 13017|    // fetch (stage only, no in-place apply) → adapters → finish, with the\n 13018|    // whole-daemon restart strictly FINAL in every shape so all other legs\n 13019|    // complete from any invoking context; `-c` composes (adapters dropped,\n...\n 17874|            assert!(\n*17875|                msg.contains(\"spt update apply --finish\"),\n 17876|                \"points at the one-shot finish: {msg}\"\n 17877|            );\n 17878|            assert!(\n\n# docs/\n## NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md\n 71|\n*72|**REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL** (impl, unit, int)\n 73|Rig-proven: `daemon stop`+`start` (the apply notice's OWN instruction) kills every hosted\n 74|endpoint; they stay OFFLINE after start (no resurrection). Fix: daemon start RE-RUNS\n 75|previously-online spt-hosted endpoints (records exist: info.json status + adapter + cwd);\n...\n 83|pair + a manual bounce. Fix: apply works daemonless (swap + record, next start runs new bytes);\n*84|`update apply --finish` (name subject to docs-token gate) completes the cycle: swap → brain\n 85|cycle → broker restart onto new bytes (riding W3's endpoint re-run so the restart is no longer\n 86|a massacre). CLI change → xtask docs gen, no internal codes in clap ///.\n 87|\n...\n 212|  log-lock/subscriber contract is KNOWN-HAZARDS-grade). Its int test IS tonight's rig.\n*213|- W3 composes: FINISH rides ENDPOINT-SURVIVAL. RC-RECONNECT-TRUTH is separable if the wave\n 214|  runs long.\n 215|- W4/W5 independent of W2/W3 — parallelizable if a second executor frees up, else sequential.\n 216|- Every wave: traceable activate → build → `traceable-reqs check` exit-0 → doyle gate\n## THE-FORKENING-TRIAGE.md\n 73|\n*74|## W4 — update UX (REQ-UPDATE-ADAPTERS-VERB, REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-RESTART-SAFE-SWAP, REQ-DAEMON-REFRESH) — queued\n 75|\n 76|Grounding fact (operator-corrected, code-confirmed): `update fetch --apply` /\n 77|`update apply` cycle the BRAIN only — the broker and every PTY survive\n 78|(apply_staged, applyhost.rs:303); \"restart required\" is a printed notice\n*79|(cli.rs:4615), not behavior. Broker restarts only under `apply --finish`.\n 80|\n 81|- `spt update adapters [<a>[,<b>...]]` — thin alias over `spt adapter update`\n 82|  (cli.rs:748; keep the old verb — published surface) + comma-list on both.\n...\n 89|  re-run machinery.\n*90|- `spt update --restart` = fetch → **adapters** → `apply --finish` LAST\n 91|  (lethal-leg-last, ruled: completes from any invoking context; a finish\n 92|  failure leaving new adapters on old core is loud + operator-attended).\n 93|  Retires findings-backlog seed #12 (REQ-UPDATE-ONE-STEP-SAFE-SWAP class).\n## W3-ENDPOINT-SURVIVAL-DESIGN.md\n*1|# W3 — REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL: design + one detection question (todlando → doyle, 2026-07-07)\n 2|\n 3|The field-accept unblocker (daemon restart no longer massacres hosted endpoints).\n 4|Your triage scope ruling: \"re-run-on-start, marked start-reason=daemon-restart;\n## W3-GATE-VERDICT.md\n 7||-----|-----|--------|\n*8|| REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL | `049b2be` | impl+unit+int |\n 9|| REQ-DAEMON-STDERR-PERSIST | `32c789e` | impl+unit |\n 10|| REQ-DAEMON-STOP-LIVE-SESSION-WARN | `ff6ebeb` (+int `8931680`) | impl+unit+int |\n 11|| REQ-RC-RECONNECT-TRUTH | `3d632b9` | impl+unit+int |\n...\n 58|**W3 GATED PASS — CLOSED.** LIFECYCLE-TRUTH W1+W2+W3 all gated. Next:\n*59|1. **W1 field-accept swap window** (operator-coordinated): `update apply --finish` IS the swap\n 60|   (ENDPOINT-SURVIVAL makes the daemon restart non-massacre); both psyche stamps self-clear on\n 61|   next commune post-swap.\n 62|2. **Release (counter 49 → v0.29.0)** via deployah (parked — operator wake pending),\n## W3-SLICE1-REPORT.md\n 4|\n*5|## REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL — the field-accept unblocker (seed #6) @049b2be\n 6|\n 7|Daemon restart no longer massacres hosted endpoints. New one-shot\n 8|`livehost::resume_restart_orphaned_endpoints` runs at the fresh broker's live-host\n## W3-WAVE-GATE-REPORT.md\n 8||-----|-----|--------|\n*9|| REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL | `049b2be` | impl+unit+int |\n 10|| REQ-DAEMON-STDERR-PERSIST | `32c789e` | impl+unit |\n 11|| REQ-DAEMON-STOP-LIVE-SESSION-WARN | `ff6ebeb` (+int fix `8931680`) | impl+unit+int |\n 12|| REQ-RC-RECONNECT-TRUTH | `3d632b9` | impl+unit+int |\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"crates, traceable-reqs.toml, docs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":49,"fileCount":11,"files":["crates/spt-daemon/src/applyhost.rs","crates/spt-daemon/src/livehost.rs","crates/spt-daemon/tests/endpoint_survival.rs","crates/spt/src/cli.rs","traceable-reqs.toml","docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md","docs/THE-FORKENING-TRIAGE.md","docs/W3-ENDPOINT-SURVIVAL-DESIGN.md","docs/W3-GATE-VERDICT.md","docs/W3-SLICE1-REPORT.md","docs/W3-WAVE-GATE-REPORT.md"],"fileMatches":[{"path":"crates/spt-daemon/src/applyhost.rs","count":2},{"path":"crates/spt-daemon/src/livehost.rs","count":9},{"path":"crates/spt-daemon/tests/endpoint_survival.rs","count":2},{"path":"crates/spt/src/cli.rs","count":14},{"path":"traceable-reqs.toml","count":11},{"path":"docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md","count":3},{"path":"docs/THE-FORKENING-TRIAGE.md","count":3},{"path":"docs/W3-ENDPOINT-SURVIVAL-DESIGN.md","count":1},{"path":"docs/W3-GATE-VERDICT.md","count":2},{"path":"docs/W3-SLICE1-REPORT.md","count":1},{"path":"docs/W3-WAVE-GATE-REPORT.md","count":1}],"truncated":true,"displayContent":"# traceable-reqs.toml\n 1985│title = \"B-2 (REMOTE-TRUTH triage §B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals — the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a tr...\n*1986│required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REMOTE-TRUTH B-2 REDUCED (todlando); int ACTIVATED on the green [twohost] rig run 28772170556 @279df99 (2026-07-06). impl = OutputLog::reap_dead_controller (broker.rs: drop a controller whose _writer.is_finished() in-memory ONLY, no stamp — lock-safe under the KIND_SESSIONS snapshot log lock) + the snapshot-closure call site (recover_log(&log) made mut; reap BEFORE reading controller_by/has_controller so the reply + the existing off-lock converge_p...\n 1987│[[requirements]]\n 1988│id = \"REQ-ADAPTER-FLOOR-ENFORCE\"\n 1989│title = \"F-5 (REMOTE-TRUTH triage §F-5 + doyle rulings 2026-07-05): BOTH adapter acquisition verbs (spt adapter add + spt adapter update) REFUSE when the installed spt-core is BELOW the adapter's declared [adapter].min_spt_core_version floor — with an F-1 operator refusal naming the installed core, the floor, and the next action (update spt-core first). ROOT: the floor was PARSED + required (manifest.rs) but never compared to the running core — dead enforcement; and the [update].version_check knob t...\n     │...\n 2114│[[requirements]]\n*2115│id = \"REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL\"\n 2116│title = \"W3 (LIFECYCLE-TRUTH): daemon restart no longer massacres hosted endpoints — daemon start RE-RUNS previously-online spt-hosted endpoints. ROOT rig-proven: daemon stop+start (the apply notice's OWN instruction) kills every hosted endpoint; they stay OFFLINE after start (no resurrection) though records exist (info.json status + adapter + cwd). SCOPE RULING (doyle): re-run-on-start, marked start-reason=daemon-restart; agents' minds ride psyche re-host as today. Int: endpoint online -> daemon stop ...\n 2117│required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED LIFECYCLE-TRUTH W3 (todlando 2026-07-07) per doyle ruling docs/W3-ENDPOINT-SURVIVAL-DESIGN.md @d257d15. impl = a one-shot resume_restart_orphaned_endpoints pass at spawn_live_host start (BEFORE the phantom-clear reconcile) that re-runs the daemon-restart-orphaned set via the shared launch_ledger_resume (launch_harness_brokered_in, resume from last ledger, is_resume, reason tag \"daemon-restart\") — extracted from resume_woken_endpoint's Resume arm. T...\n 2118│\n     │...\n 2120│id = \"REQ-UPDATE-ONE-SHOT-FINISH\"\n*2121│title = \"W3 (LIFECYCLE-TRUTH): update apply works daemonless and one command finishes the cycle. ROOT (operator wart): update fetch/apply run ensure_daemon_announced (cli.rs:4386) -> on a stopped box they BOOT THE OLD broker pre-swap, guaranteeing the mixed old-broker/new-brain pair + a manual bounce. FIX: apply works daemonless (swap + record, next start runs new bytes); `update apply --finish` (name subject to docs-token gate) completes the cycle: swap -> brain cycle -> broker restart onto new bytes (r...\n*2122│required_stages = [\"impl\", \"unit\"]  # ACTIVATED LIFECYCLE-TRUTH W3 (todlando 2026-07-07). impl = (a) applyhost::apply_staged_daemonless — the swap+record apply with NO broker connect/brain-restart (shared prepare_apply preamble + swap_and_record helper factored out of apply_staged), returning the new ApplyStagedOutcome::AppliedDaemonless; (b) cmd_update_apply drops ensure_daemon_announced (no more old-broker boot pre-swap): when the daemon is up it hands off in place (apply_staged), when it is down it ...\n 2123│\n 2124│[[requirements]]\n 2125│id = \"REQ-DAEMON-STOP-LIVE-SESSION-WARN\"\n 2126│title = \"W3 (LIFECYCLE-TRUTH, promoted old follow-wave seed): `daemon stop` with live hosted sessions warns + requires --force (or names the sessions it will kill) instead of silently killing them.\"\n*2127│required_stages = [\"impl\", \"unit\"]  # ACTIVATED LIFECYCLE-TRUTH W3 (todlando 2026-07-07). impl = a --force flag on `daemon stop` + cmd_daemon_stop queries the live hosted session set (live_hosted_session_ids via the broker sessions()) and, without --force, REFUSES (exit 3) naming the sessions the stop would kill (with the reassurance they come back on the next start — composes with REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL); --force stops anyway. CLI change -> xtask gen regenerated docs-site/src/cli/referenc...\n 2128│\n 2129│[[requirements]]\n 2130│id = \"REQ-RC-RECONNECT-TRUTH\"\n     │...\n 2175│id = \"REQ-UPDATE-FINISH-COMMUNE-FLUSH\"\n*2176│title = \"DEFERRED (post-LIFECYCLE-TRUTH, operator-ruled 2026-07-07 — mint now, impl a FUTURE milestone): make the update swap LOSSLESS for live hosted endpoints by flushing a final echo-commune per endpoint BEFORE the brain-subtree reap. ROOT (operator-surfaced probing --finish): `update apply --finish` = daemonless swap -> daemon RESTART; the graceful `daemon stop` path (daemon.rs:316-325) raises brain_stop then reaper.reap() KILLS the brain subtree (brain + shellwake watchers + detached Psyches) as o...\n 2177│required_stages = []  # minted DEFERRED (doyle 2026-07-07, operator ruled impl waits until after LIFECYCLE-TRUTH); activate at the future milestone that delivers swap-loss-free updates\n 2178│\n 2179│[[requirements]]\n     │...\n 2299│id = \"REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK\"\n*2300│title = \"UPDATE-WEDGE round-4 (hertz RCA, root reproduced + source-pinned on Windows 2026-07-09): EVERY write on a physical broker connection rides ONE bounded + cancelable + poison-on-failure framed-write primitive — no writer may hold the connection's serialized send gate across an UNBOUNDED OS write. ROOT: controller_writer held the SharedSend = Arc<Mutex<SendHalf>> guard ACROSS a blocking write_frame; on Windows interprocess 2.4.2 routes the send to WriteFileEx + SleepEx(INFINITE, alertable) with N...\n 2301│required_stages = [\"doc\", \"impl\", \"int\"]  # ACTIVATED at mint, UPDATE-WEDGE round-4 (todlando 2026-07-09) per doyle GO. This ACTIVATES (and replaces, dedup at the v0.30.6 merge) the round-3 deferred SEED of the same id — r4 IS the Option-C class fix that SEED reserved; broker-side ⇒ lands via coordinated daemon restart, not a seamless brain-swap. doc = KNOWN-HAZARDS 7.38. impl = conn.rs BrokerConn (gate+watchdog+poison+abort) + full write-site routing sweep. int = brain_decouple.rs non_draining_contr...\n 2302│\n 2303│# ── MSG-IDENTITY milestone (operator GO 2026-07-10: \"two triages + prominent backlog seeds, especially messaging + endpoint identity\"; F-037 EXCLUDED by operator, covered later) ──\n     │...\n 2386│id = \"REQ-UPDATE-DEFAULT-COMPOSITE\"\n*2387│title = \"THE-FORKENING W4 (operator-grilled 2026-07-14): plain `spt update` = `update fetch --apply` THEN `update adapters` (core-first doctrine order); when core is already current the core leg no-ops and ONLY adapters update; `--core-only`/`-c` skips the adapters leg. GROUNDING (operator-corrected, code-confirmed): fetch --apply cycles the BRAIN only — broker + PTYs survive (apply_staged applyhost.rs:303; the restart-required text is a NOTICE, cli.rs:4615, not behavior) — so the composite's invokin...\n 2388│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2389│\n 2390│[[requirements]]\n*2391│id = \"REQ-UPDATE-RESTART-SAFE-SWAP\"\n*2392│title = \"THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap — fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act — everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish F...\n 2393│required_stages = [\"doc\", \"impl\", \"unit\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2394│\n 2395│[[requirements]]\n     │...\n 2438│id = \"REQ-REDISPATCH-FINISHED-RETIRE\"\n*2439│title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bound...\n 2440│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16; int ADDED gate round 1; RULING gate round 2: retirement is ELIGIBILITY-VISIBILITY, never teardown — the \"lifecycle-exclude\" arm of the title, remove is REJECTED: the worker's reply send is fire-and-forget, so remove-on-retire races the still-queued reply inside the broker and resets the never-finished send half = requester torn-EOF/NoReply, proven 30/150 vs 0/150 A/B on kitsubito; physical teardown b...\n 2441│\n 2442│[[requirements]]\n\n# crates/\n\n## spt-daemon/\n\n### src/\n#### applyhost.rs\n 376│/// the swapped bytes run when the daemon is NEXT started (either by the operator,\n*377│/// or by `update apply --finish` which restarts it in the same command, riding\n*378│/// REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL so hosted endpoints come back). The\n 379│/// recorded `AppliedPending` promotes on that fresh brain's `ready`, exactly as\n 380│/// the handoff path's does. Runs the identical verify/classify gate as\n 381│/// [`apply_staged`], so every fail-closed refusal is preserved.\n#### livehost.rs\n  423│/// stays daemon-authoritative, the D9-2 posture the resume leg already keeps).\n *424│// [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n  425│#[allow(clippy::too_many_arguments)] // resume material + the two registry lookups; a struct would obscure the call\n  426│fn launch_ledger_resume(\n  427│    perch: &Path,\n     │...\n  482│\n *483│/// The daemon-restart re-run decision (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL). A pure\n  484│/// function over a candidate perch's facts, so the full belt matrix is unit-tested\n  485│/// without a broker. Only the daemon-restart-ORPHANED, spt-hosted, DEAD set re-runs:\n  486│/// - `state != live_agent` → Skip (not a live agent).\n     │...\n  498│///   a live custody process; refuse loudly, the breadcrumb feeds the W4 dup-wake rig).\n *499│// [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n  500│#[derive(Debug, PartialEq, Eq)]\n  501│enum RestartResume {\n  502│    Resume,\n     │...\n  506│\n *507│// [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n  508│fn restart_resume_gate(\n  509│    state: &str,\n  510│    status: Option<&str>,\n     │...\n  536│\n *537│/// LIFECYCLE-TRUTH W3 (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL): on daemon start, RE-RUN the\n  538│/// harness of every previously-ONLINE spt-hosted endpoint this daemon (re)start\n  539│/// orphaned — so a `daemon stop`+`start` (the apply notice's OWN instruction) no longer\n  540│/// massacres them (seed #6, the field-accept blocker). Runs ONCE at\n     │...\n  548│/// hiccup — the safe direction, same posture as [`reconcile_hosted_liveness`]).\n *549│// [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n  550│pub fn resume_restart_orphaned_endpoints(\n  551│    owlery: &Path,\n  552│    registered: &[(AdapterRecord, Manifest)],\n     │...\n  996│            // LIFECYCLE-TRUTH W3 ONE-SHOT daemon-restart endpoint survival\n *997│            // (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL): re-run the harness of every\n  998│            // previously-ONLINE spt-hosted endpoint this (re)start orphaned, BEFORE the\n  999│            // reconcile loop's phantom-clear offlines the stale-online records (the \"was\n 1000│            // online\" signal). A brain restart with a surviving broker re-runs nothing\n 1001│            // (its sessions are live, not orphaned). Belts inside the gate.\n*1002│            // [impl->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n 1003│            resume_restart_orphaned_endpoints(&owlery, &registered, &adapters_dir);\n 1004│        }\n 1005│        while !stop.load(Ordering::SeqCst) {\n     │...\n 2095│\n*2096│    // [unit->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL] the daemon-restart re-run GATE + both\n 2097│    // belts, as a pure matrix. Only a previously-ONLINE, spt-hosted (controllable),\n 2098│    // orphaned (no live session), non-relay, DEAD-custody endpoint re-runs; every other\n 2099│    // class is excluded, and a LIVE custody pid refuses (the dup guard, not a silent skip).\n\n### tests/\n#### endpoint_survival.rs\n *1│//! LIFECYCLE-TRUTH W3 (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL) — daemon-restart endpoint\n  2│//! survival, against a REAL broker over the real local IPC.\n  3│//!\n  4│//! ## The bug this gate locks down (seed #6, the field-accept blocker)\n   │...\n 84│\n*85│// [int->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n 86│#[test]\n 87│fn daemon_restart_reruns_a_previously_online_orphaned_endpoint() {\n 88│    let home = tempfile::TempDir::new().expect(\"temp home\");\n\n## spt/src/\n### cli.rs\n   251│        /// The full-cycle form: fetch, update adapters, then finish by\n  *252│        /// restarting the daemon onto the new version (`update apply --finish`)\n   253│        /// as the final step — so the whole node, coordinator and live agents,\n   254│        /// runs the new version when it returns. The restart bounces hosted\n   255│        /// sessions (they come back automatically).\n      │...\n  1454│            // [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n *1455│            // [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\n  1456│            None => cmd_update_composite(core_only, restart),\n  1457│        },\n  1458│        // [impl->REQ-INSTALL-BOOTSTRAP-VERB]\n      │...\n  4903│        \"Installed spt-core {ver} on disk. The daemon is offline, so nothing is running the new \\\n *4904│         version yet — run `spt update apply --finish` to bring it up on the new version now (any \\\n  4905│         hosted sessions come back automatically), or `spt daemon start`.\\nChangelog: {RELEASES_URL}\"\n  4906│    )\n  4907│}\n      │...\n  5043│/// One leg of the bare `spt update` composite (REQ-UPDATE-DEFAULT-COMPOSITE,\n *5044│/// REQ-UPDATE-RESTART-SAFE-SWAP).\n  5045│#[derive(Debug, PartialEq, Eq, Clone, Copy)]\n  5046│enum UpdateLeg {\n  5047│    /// `update fetch --apply` — stage + install the core in one step (the\n      │...\n  5053│    Adapters,\n *5054│    /// `update apply --finish` — swap + full daemon restart. LETHAL: from an\n  5055│    /// spt-hosted session the invoker's PTY dies here, so this leg is always\n  5056│    /// planned LAST (everything else has completed by then).\n  5057│    Finish,\n      │...\n  5066│// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n *5067│// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\n  5068│fn plan_update_legs(core_only: bool, restart: bool) -> Vec<UpdateLeg> {\n  5069│    let mut legs = Vec::new();\n  5070│    legs.push(if restart {\n      │...\n  5088│/// PTY keep running (applyhost::apply_staged) — so no re-run machinery exists.\n *5089│/// With `--restart` (REQ-UPDATE-RESTART-SAFE-SWAP) the plan is fetch → adapters\n *5090│/// → `apply --finish` LAST: the one-step safe full-cycle swap.\n  5091│///\n  5092│/// Failure policy: a CORE-leg failure (fetch/apply refused, gh missing…)\n  5093│/// ABORTS the remaining legs — updating adapters against a channel that just\n      │...\n  5100│// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n *5101│// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\n  5102│fn cmd_update_composite(core_only: bool, restart: bool) -> i32 {\n  5103│    let mut worst = 0;\n  5104│    for leg in plan_update_legs(core_only, restart) {\n      │...\n  5224│\n *5225│/// `spt update apply --finish` — swap the bytes daemonless (never leaving an old\n  5226│/// coordinator running new-agent code), then RESTART the daemon onto them so the\n  5227│/// whole node runs the new version in one command. The restart rides\n *5228│/// REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL: `daemon start` re-runs the previously\n  5229│/// online hosted endpoints, so the cycle is not a massacre.\n  5230│// [impl->REQ-UPDATE-ONE-SHOT-FINISH]\n  5231│fn cmd_update_apply_finish(\n      │...\n 12840│    // ── THE-FORKENING W4: the update-UX cluster (REQ-UPDATE-ADAPTERS-VERB,\n*12841│    // REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-RESTART-SAFE-SWAP,\n 12842│    // REQ-DAEMON-REFRESH). ──\n 12843│\n 12844│    // [unit->REQ-UPDATE-ADAPTERS-VERB] comma-list parsing shared by both verb\n      │...\n 13015│\n*13016│    // [unit->REQ-UPDATE-RESTART-SAFE-SWAP] lethal-leg-LAST: `--restart` plans\n 13017│    // fetch (stage only, no in-place apply) → adapters → finish, with the\n 13018│    // whole-daemon restart strictly FINAL in every shape so all other legs\n 13019│    // complete from any invoking context; `-c` composes (adapters dropped,\n      │...\n 17874│            assert!(\n*17875│                msg.contains(\"spt update apply --finish\"),\n 17876│                \"points at the one-shot finish: {msg}\"\n 17877│            );\n 17878│            assert!(\n\n# docs/\n## NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md\n  71│\n *72│**REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL** (impl, unit, int)\n  73│Rig-proven: `daemon stop`+`start` (the apply notice's OWN instruction) kills every hosted\n  74│endpoint; they stay OFFLINE after start (no resurrection). Fix: daemon start RE-RUNS\n  75│previously-online spt-hosted endpoints (records exist: info.json status + adapter + cwd);\n    │...\n  83│pair + a manual bounce. Fix: apply works daemonless (swap + record, next start runs new bytes);\n *84│`update apply --finish` (name subject to docs-token gate) completes the cycle: swap → brain\n  85│cycle → broker restart onto new bytes (riding W3's endpoint re-run so the restart is no longer\n  86│a massacre). CLI change → xtask docs gen, no internal codes in clap ///.\n  87│\n    │...\n 212│  log-lock/subscriber contract is KNOWN-HAZARDS-grade). Its int test IS tonight's rig.\n*213│- W3 composes: FINISH rides ENDPOINT-SURVIVAL. RC-RECONNECT-TRUTH is separable if the wave\n 214│  runs long.\n 215│- W4/W5 independent of W2/W3 — parallelizable if a second executor frees up, else sequential.\n 216│- Every wave: traceable activate → build → `traceable-reqs check` exit-0 → doyle gate\n## THE-FORKENING-TRIAGE.md\n 73│\n*74│## W4 — update UX (REQ-UPDATE-ADAPTERS-VERB, REQ-UPDATE-DEFAULT-COMPOSITE, REQ-UPDATE-RESTART-SAFE-SWAP, REQ-DAEMON-REFRESH) — queued\n 75│\n 76│Grounding fact (operator-corrected, code-confirmed): `update fetch --apply` /\n 77│`update apply` cycle the BRAIN only — the broker and every PTY survive\n 78│(apply_staged, applyhost.rs:303); \"restart required\" is a printed notice\n*79│(cli.rs:4615), not behavior. Broker restarts only under `apply --finish`.\n 80│\n 81│- `spt update adapters [<a>[,<b>...]]` — thin alias over `spt adapter update`\n 82│  (cli.rs:748; keep the old verb — published surface) + comma-list on both.\n   │...\n 89│  re-run machinery.\n*90│- `spt update --restart` = fetch → **adapters** → `apply --finish` LAST\n 91│  (lethal-leg-last, ruled: completes from any invoking context; a finish\n 92│  failure leaving new adapters on old core is loud + operator-attended).\n 93│  Retires findings-backlog seed #12 (REQ-UPDATE-ONE-STEP-SAFE-SWAP class).\n## W3-ENDPOINT-SURVIVAL-DESIGN.md\n*1│# W3 — REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL: design + one detection question (todlando → doyle, 2026-07-07)\n 2│\n 3│The field-accept unblocker (daemon restart no longer massacres hosted endpoints).\n 4│Your triage scope ruling: \"re-run-on-start, marked start-reason=daemon-restart;\n## W3-GATE-VERDICT.md\n  7│|-----|-----|--------|\n *8│| REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL | `049b2be` | impl+unit+int |\n  9│| REQ-DAEMON-STDERR-PERSIST | `32c789e` | impl+unit |\n 10│| REQ-DAEMON-STOP-LIVE-SESSION-WARN | `ff6ebeb` (+int `8931680`) | impl+unit+int |\n 11│| REQ-RC-RECONNECT-TRUTH | `3d632b9` | impl+unit+int |\n   │...\n 58│**W3 GATED PASS — CLOSED.** LIFECYCLE-TRUTH W1+W2+W3 all gated. Next:\n*59│1. **W1 field-accept swap window** (operator-coordinated): `update apply --finish` IS the swap\n 60│   (ENDPOINT-SURVIVAL makes the daemon restart non-massacre); both psyche stamps self-clear on\n 61│   next commune post-swap.\n 62│2. **Release (counter 49 → v0.29.0)** via deployah (parked — operator wake pending),\n## W3-SLICE1-REPORT.md\n 4│\n*5│## REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL — the field-accept unblocker (seed #6) @049b2be\n 6│\n 7│Daemon restart no longer massacres hosted endpoints. New one-shot\n 8│`livehost::resume_restart_orphaned_endpoints` runs at the fresh broker's live-host\n## W3-WAVE-GATE-REPORT.md\n  8│|-----|-----|--------|\n *9│| REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL | `049b2be` | impl+unit+int |\n 10│| REQ-DAEMON-STDERR-PERSIST | `32c789e` | impl+unit |\n 11│| REQ-DAEMON-STOP-LIVE-SESSION-WARN | `ff6ebeb` (+int fix `8931680`) | impl+unit+int |\n 12│| REQ-RC-RECONNECT-TRUTH | `3d632b9` | impl+unit+int |","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784247426558}}
{"type":"message","id":"cbf66037","parentId":"d94a8c29","timestamp":"2026-07-17T00:17:06.560Z","message":{"role":"toolResult","toolCallId":"call_EW4IQgzivPTD3f7bFEBmVZeu|fc_099a7567dadef391016a5974826294819b80013c8db9cb023c","toolName":"grep","content":[{"type":"text","text":"# .github/workflows/\n## release.yml\n 140|          cargo run -p xtask -- docs-bundle dist/spt-docs.tar.gz\n*141|      - name: Build user-facing release notes from CHANGELOG\n*142|        # The release BODY is the user-facing changelog: the `## [<version>]`\n*143|        # section of CHANGELOG.md (Added / Changed / Fixed, UX items only — no\n 144|        # internal lingo). Authoring that section before tagging is a required\n 145|        # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the\n 146|        # release loudly rather than shipping an empty/boilerplate body — the\n*147|        # changelog is not optional.\n 148|        run: |\n 149|          ver=\"${GITHUB_REF_NAME#v}\"   # v0.1.2 -> 0.1.2\n 150|          notes=\"dist/RELEASE_NOTES.md\"\n...\n 156|            grab { print }\n*157|          ' CHANGELOG.md > \"$notes\"\n 158|          if ! grep -q '[^[:space:]]' \"$notes\"; then\n*159|            echo \"::error::CHANGELOG.md has no '## [$ver]' section — add the user-facing changelog for this release before tagging (see docs/RELEASE-RUNBOOK.md).\"\n 160|            exit 1\n 161|          fi\n 162|          echo \"----- release body for ${GITHUB_REF_NAME} -----\"\n...\n 177|            --title \"spt ${GITHUB_REF_NAME}\" \\\n*178|            --notes-file dist/RELEASE_NOTES.md \\\n 179|            dist/spt-x86_64-* \\\n 180|            dist/SHA256SUMS \\\n 181|            dist/manifest.schema.json \\\n\n# docs/\n## DEFERRED.md\n 47|| **Cross-node shell TUNNEL relay (REQ-SHELL-4 cross-node-on-LAN carry)**: W3 built + proved the opaque byte tunnel **same-node only** (broker-homed `TunnelHub` loopback stream pair + the local control socket; `tunnel_e2e` round-trips `<EVENT`-looking bytes byte-exact). The **cross-node** path does NOT exist: `StreamFamily` (dispatch.rs) has no `Tunnel` variant, `classify_first_line` no tunnel arm, `SHELL_LINK` carries only relink/cmd/drive (no tunnel action), and every tunnel surface (`tunnel_ensure/sen...\n*48|| **Per-connection contiguous-sent cursor — structural guard against a decreasing-floor controller re-take (`REQ-HAZARD-CONTROLLER-WRITER-REORDER` follow-up)**: v0.13.0 P1c fixes the controller-writer reorder while KEEPING handoff's eager subscribe (fix #1 \"drop the subscribe\" was reverted — it's the standalone-resume mechanism), so the decreasing-floor double-take (handoff subscribe@K + serve_attach subscribe@0) is still PRESENT and made safe by: `controller_writer` epoch-gated under `send.lock()` (...\n 49|| **Adapter-manifest-enabled rc VT/mouse mode (`spt rc` scroll vs right-click-paste xor resolution)**: v0.13.0 P1/P1b's Windows `EnableMouseCapture` (for client-originated right-click bracketed paste, 7.18) makes Windows Terminal hand ALL wheel events to rc instead of scrolling its own scrollback; rc forwards the wheel to the harness as SGR reports (7.20) but ONLY when the harness has mouse-reporting on (`enabled && sgr`) — and **Claude Code's REPL never enables mouse tracking** (it has no internal scr...\n 50|| **Always-on endpoint kind + `#`-sigil addressing (ADR-0023; REQ-EP-8 / REQ-EP-9) — the core seam for adapter-bridged group/forum messaging**: a `[always-on]` manifest section declaring a resident, owner-less, addressable endpoint binary the daemon boot-launches + supervises **continuously** (independent of agent liveness, register-triggered, one per `<adapter>[:profile]`), which self-manages its `#`-addressed channel endpoints via the existing `api bind` and may call the already-ungated `endpoint wak...\n## KNOWN-HAZARDS.md\n 26|- **spt-core mapping:** cross-node Psyche sync (ADR-0002/0003) replaces git-repo sync, so the *git* lock may disappear — but any equivalent lockfile in the new sync mechanism needs the same stale-sweep on boot.\n*27|- **Sister cite:** CHANGELOG v1.11.20 \"Stale `index.lock`\"; `src/common/git.rs`.\n 28|\n 29|### 1.4 Deferred spool rows must not leak to the event stream\n 30|- **Failure:** a hook spools a deferred (spool-only, no TCP wake) notice; startup `drain_all` flushes ALL rows including deferred → event emitted at wrong time/priority.\n...\n 48|- **spt-core mapping:** session binding (parent-process-tree anchor) still applies for harness-hosted topology. For spt-hosted sessions the broker holds the child directly → liveness is the broker's held-handle state, more reliable than PID polling.\n*49|- **Sister cite:** `src/live/wrapper/orphan.rs:141-161`; CHANGELOG v1.11.20.\n 50|\n 51|### 2.2 Stdin session_id precedence over env\n 52|- **Failure:** subagent inherits a stale `OWL_SESSION_ID` env across `/clear`; hook gets two session_ids (fresh stdin, stale env) → wrong-agent binding.\n...\n 54|- **spt-core mapping:** the harness-contract subcommand surface must define the same precedence for whatever identity fields hooks pass in.\n*55|- **Sister cite:** CHANGELOG v1.35.1 \"IN-05\"; `hook_subagent_start.rs:40-51`.\n 56|\n 57|### 2.3 Binary-handoff argv schema must stay backward-compatible\n 58|- **Failure:** old binary spawns new binary with old argv arity; clap rejects before state rehydration → wrapper dies unlogged.\n...\n 60|- **spt-core mapping:** CRITICAL — self-update (ADR-0004) makes handoff routine. The broker↔brain IPC and any brain-relaunch argv must be versioned and forward/backward tolerant (a newer brain talks to an older broker). This is the single most update-frequency-sensitive invariant.\n*61|- **Sister cite:** `src/live/wrapper/lifecycle.rs:17-106`; `src/cli.rs` defaults; CHANGELOG v1.11.10.\n 62|\n 63|### 2.4 Generation `gen_start` always = now() on cold-start AND handoff\n 64|- **Failure:** stale gen_start from a rehydrated state file fires time-based discriminators on the new process.\n...\n 89|- **spt-core mapping:** same sweep on daemon (re)start per hosted instance.\n*90|- **Sister cite:** CHANGELOG v1.11.20; `src/owl/cleanup.rs:97`.\n 91|\n 92|### 3.3 Orphan teardown fires echo-commune BEFORE INIT_SIGNOFF\n 93|- **Failure:** teardown delivers INIT_SIGNOFF without first saving the final context delta → Psyche signoff lacks the context-save summary.\n...\n 123|- **Invariant:** deferred rows are never flushed by the event-stream drain; only `drain_non_deferred_*` / `peek_all` touch them.\n*124|- **Sister cite:** CHANGELOG v1.11.20; `src/common/spool.rs`. (See also 1.4.)\n 125|\n 126|### 4.5 Inbox legacy compat must not double-deliver\n 127|- **Failure:** message surfaced via both spool (durable) and legacy inbox files → duplicate or racing delivery.\n...\n 185|- **spt-core mapping:** all on-disk state writes (registry, trust store, spool checkpoints) use this pattern. Self-update binary swap on Windows especially.\n*186|- **Sister cite:** CHANGELOG \"EBUSY\"; `src/common/owlery.rs` atomic_write.\n 187|\n 188|### 5.3 Git/subprocess timeout stamping\n 189|- **Failure:** a hung subprocess (git on slow net) blocks the supervisor indefinitely.\n...\n 292|- **spt-core mapping:** clean greenfield layout from day one (no migration window) — pick one structure, route everything through the registry. Storage layout deferred to design phase but this single-source-of-truth rule is binding.\n*293|- **Sister cite:** `src/common/perch_path.rs`; CHANGELOG Phase 25.4.\n 294|\n 295|### 6.2 Soft-cleanup preserves state, removes `ready`\n 296|- **Failure:** hard-deleting a perch on cleanup loses spool (incl. stored signoff) needed for offline recovery.\n...\n 304|- **spt-core mapping:** any destructive maintenance command must check for live child instances first.\n*305|- **Sister cite:** CHANGELOG v1.11.20 Phase 35.1.\n 306|\n 307|### 6.4 Drop files are single-writer (supervisor-owned), read-only for the mind\n 308|- **Failure:** the Psyche LLM deletes a commune drop file the wrapper is concurrently reading → race, lost commune.\n...\n 310|- **spt-core mapping:** the daemon is the single writer; the harness-invoked mind never mutates drop files. Bake the ownership into the runtime contract.\n*311|- **Sister cite:** CHANGELOG v1.11.7; `src/live/context.rs:615` (removed delete).\n 312|\n 313|### 6.5 Direct-write precedence guard against stale LLM overwrites\n 314|- **Failure:** the LLM emits an older snapshot that clobbers a fresher direct write to a context file.\n...\n 316|- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity **plus a per-node version vector** (entries from each node's monotonic `EpochSource`; wall-clock never orders). Distributed rule (ADR-0013, M4-D6): dominate→accept, dominated→drop, **concurrent→surface as durable replicated conflict artifacts + Psyche-reconcile on the active instance's node — never silent newest-wins, never lose either version**. T...\n*317|- **Sister cite:** CHANGELOG v1.11.6; `src/owl/echo_commune.rs`.\n 318|\n 319|### 6.6 Surfaced context conflicts preserve both versions until dominated\n 320|- **Failure:** a cross-node concurrent context write (version vectors, neither dominates) gets auto-picked or partially dropped — half a mind silently lost.\n## MSG-IDENTITY-DISPATCH.md\n 93|  doyle for fix-shape ruling BEFORE build.\n*94|- Release: deployah, counter from published metadata, bump rides IN the release PR. CHANGELOG\n 95|  census = field symptoms across all six waves (dup deliveries, seat-theft, lost communes,\n 96|  bringup dead-ends, gateway-down-after-restart, poison log floods).\n 97|\n## MUSL-RELEASE-HANDOFF.md\n 2|\n*3|**W3 gate = PASS 2026-07-08 (doyle).** Branch `feat/musl-w3-release-artifact` @`7d776ea` (off `main`@`047db7d`). This is the milestone-completing wave — the release cut is how the musl artifact reaches the field. Your lane ([[dont-solo-across-role-lines]]): bump + CHANGELOG + release PR + tag + publish + real-node verify.\n 4|\n 5|## What's gated (don't re-gate the code — verify the PUBLISH)\n 6|- release.yml musl matrix (kitsubito, `CC_x86_64_unknown_linux_musl=musl-gcc`, in-job musl-tools+cmake+target); assemble globs `spt-x86_64-*` into SHA256SUMS + upload; xtask signs + update-set-enters musl by registry-derived triple (zero xtask code change post-W1).\n...\n 10|1. On the W3 branch: bump VERSION + Cargo.lock first-party ([[release-Cargo.lock-first-party]]); counter from PUBLISHED spt-releases metadata ([[release-counter-from-published]]) — last was v0.29.1=counter 50, so expect **v0.30.0 = counter 51** (minor: additive new platform), CONFIRM from published not memory.\n*11|2. CHANGELOG: user-facing surface = \"static-musl Linux artifact (`spt-x86_64-linux-musl`) — runs on pre-glibc-2.39 hosts; `spt update fetch` now selects+verifies it. gnu stays the default Linux artifact.\" Scope by observable surface ([[changelog-scope-vs-commit-range]]).\n 12|3. Release PR (milestone+release, [[release-standard-bump-in-pr]]), CI both legs green, merge → tag on merge.\n 13|4. Tag fires release.yml — **the FIRST real CI musl build happens here** (the musl matrix runs on tag, not PR). De-risked: I built it clean on kitsubito. Watch the kitsubito musl leg; if it flakes on CI-runner contention, [[seedmap-test-collides-live-daemon]] (kitsubito = musl-build box AND gnu runner) — sequence/sweep, rerun `--failed` (no re-tag).\n 14|5. **Close the field gap for real:** after publish, on a musl node run `spt update fetch` → must now get the musl artifact (NO NoArtifactForPlatform) → verify SHA256+sig over musl bytes → apply → self-updates → self-IDs musl at the new version. THAT is the full self-update E2E completing on the published release (my gate proved every leg upstream of the published artifact).\n## NEXT-MILESTONE-BUG-TRIAGE.md\n 217|## Operator rulings logged\n*218|- **#11:** picker remote-detail + status palette DID ship in v0.17.0 (W5) — CHANGELOG bullet \"endpoint picker shows remote endpoints in full detail\" + the lifecycle \"Reading the picker\" legend; it just didn't spell out \"colored squares.\" #11 here = bring the non-interactive `endpoint list` to that same palette (NOT yet shipped).\n 219|- **#14:** confirmed — switch the top-right ID badge OFF next release; save the concept for a future web SPT GUI. **NON-NEGOTIABLE: accurate PTY representation with zero artifacts** (raises the bar on #6/#7/#8 — a real fix, not just disabling the badge).\n 220|- **#6:** server-side grid approach confirmed correct.\n 221|- **#15:** display-fix path accepted.\n## NEXT-MILESTONE-MUSL-TIER-TRIAGE.md\n 34|release.yml gains a musl matrix entry (build on kitsubito; install `musl-tools`+`cmake`+target in-job, `CC_x86_64_unknown_linux_musl=musl-gcc`); the assemble job includes `spt-x86_64-linux-musl` in SHA256SUMS + the release upload; `release-publish` (xtask) signs the musl artifact; the update-set carries its artifact entry.\n*35|- **Gate (release-pipeline touch → real E2E):** cut a draft/test release with the musl artifact; a **static musl binary on a sub-2.39-glibc box** runs `spt update fetch` → gets the musl artifact (no `NoArtifactForPlatform`), applies, and self-updates. Verify SHA256 + signature over the musl bytes. Bump/CHANGELOG ride the release PR ([[release-standard-bump-in-pr]]); deployah publishes.\n 36|\n 37|## Notes / rulings pre-banked\n 38|- **No source-dependency changes** — feasibility proved the current tree links static musl as-is. Do NOT swap the crypto provider (aws-lc-sys builds clean on musl here); keep the tree identical.\n## REDISPATCH-TRUTH-TRIAGE.md\n 2|\n*3|> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first\n 4|> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:\n 5|> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n 6|> (hertz source-level, doyle seam-verified same day). Decision record:\n...\n 41|2/3): 4/5 endpoints frozen across both 2026-07-16 doyle refreshes\n*42|(deployah-recovery + the v0.33.0 apply) + lia on the update;\n 43|`DISPATCH:N:Failed` lines with low/historical stream ids in fresh\n 44|generations = the indiscriminate-replay supporting evidence.\n 45|\n## RELEASE-RUNBOOK.md\n 23|   matching first-party `spt-*` lines in `Cargo.lock` — leave third-party lines\n*24|   alone), the regenerated docs (`cargo run -p xtask -- gen`), and the changelog\n 25|   section (step 2) **in one PR**: the feature PR's final commit, or a dedicated\n 26|   release PR when the work already merged. That PR's CI validates the **exact\n 27|   commit that gets tagged**, so on merge you tag straight away — the green PR run\n...\n 31|     to what the feature PR already proved green (only the version string,\n*32|     changelog, and lockfile lines differ); that second pass is ~one CI cycle of\n 33|     pure latency.\n 34|   - Merging the release PR still fires `push:[main]` CI automatically — let it\n 35|     run as a **non-blocking backstop**; it is **not** a tag gate. Tag the merged\n 36|     `main` HEAD as soon as the PR is merged.\n*37|2. **Write the user-facing changelog** — add a `## [0.X.Y] - <date>` section at\n*38|   the top of `CHANGELOG.md` (under the intro) with **Added / Changed / Fixed**\n 39|   subsections. This section becomes the GitHub Release **body** verbatim\n*40|   (`release.yml` extracts it via `--notes-file`), so it is the changelog every\n 41|   spt user reads. Rules:\n 42|   - **spt-user-facing UX only.** What a person running the `spt` CLI notices or\n 43|     does differently. Name the actual commands/flags they type.\n...\n 48|   - The release **fails loudly** if the tagged version has no `## [0.X.Y]`\n*49|     section — the changelog is not optional.\n*50|   - **After inserting the new section, assert the version ladder is\n*51|     contiguous**: `grep '^## \\[' CHANGELOG.md` and check the versions descend\n 52|     with no gap. Inserting the new heading with an editor whose anchor spans the\n 53|     previous heading can silently *consume* that heading (v0.34.0 did this to\n*54|     `## [0.33.0]`), folding the prior release's body into the new section — and\n*55|     since the body is published verbatim, the release notes then carry two\n 56|     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n 57|3. **Tag**: `git tag v0.X.Y && git push origin v0.X.Y`. This triggers:\n 58|   - `release.yml` — both runners build, the assemble job extracts this\n*59|     version's `CHANGELOG.md` section into the release body and creates a\n 60|     **draft** release on spt-releases with `spt-x86_64-linux`,\n 61|     `spt-x86_64-windows.exe`, `SHA256SUMS`, `manifest.schema.json`,\n 62|     `mock-adapter.zip`;\n## THE-FORKENING-TRIAGE.md\n 36|  cli.rs:5363; xtask REPO const, xtask main.rs:729;\n*37|  notif.rs CONSENT_CHANGELOG_URL rider).\n 38|- Loud failure tokens: gh missing → `UPDATE_FETCH_REJECTED:GhCliRequired`\n 39|  with OS-specific install hints (winget/apt/brew); gh unauthed → distinct\n 40|  `GhAuthRequired` pointing at `gh auth login`.\n## W6-GATE-VERDICT.md\n 66|\n*67|CHANGELOG census covers v0.28.0..HEAD (the counter-49 cut).\n\n## adr/\n### 0019-digest-adapter-declared-extractor-session-spanning.md\n 8|\n*9|M9 reconciled the digest onto the log source and, in its plan §2, chose \"approach A\": a single published `{role,text,tool}` contract carried in the adapter's `[history]` records, **no digest-specific manifest seam** — the adapter's existing `[history]` normalizer was expected to emit the contract, and the v0.5.0 release notes told adapter authors exactly that. This decision was made inside the milestone and **was not approved by the operator**, who reopened it on review. Four problems are real:\n 10|\n 11|1. **Dual-consumer conflict.** One `[history]` source feeds *two* consumers with opposite needs: the **echo-commune** pipes records *opaque and verbatim* to a summarizer (it wants full-fidelity records), while the **digest** needs them to *parse* as the contract. One normalizer cannot serve both — emit rich records and the digest silently drops every line; emit contract records and echo loses fidelity.\n 12|2. **Silent failure.** `spt_term::projection::record_to_tagged` is `serde_json::from_str(raw).ok()` — any line that is not valid contract JSON is dropped with no count, sample, or log. A normalizer that does not exactly match the contract yields an **empty digest with zero signal**; the author's only symptom is \"`spt endpoint digest` returns nothing.\"\n### 0036-private-fork-distribution.md\n 64|- Version-matched docs replace always-latest docs — an upgrade for agents,\n*65|  a regression only for reading future release notes, which remain in the\n 66|  release entries on the channel.\n\n## spikes/\n### SPIKE-01-broker-handoff.md\n 18|\n*19|Invariants: **A** child alive whole run (no PTY EOF) · **B** `tick N` counter contiguous · **C** input reaches child after brain restart · **D** client stream contiguous across restart.\n 20|\n 21|## Result\n 22|\n 23|```\n*24|child pid 33880 survived | pty ticks 0..39 contiguous, no EOF\n 25|post-restart input echoed | client: 34 ticks, no gap across the brain kill/restart window\n 26|A PASS  B PASS  C PASS  D PASS  →  OVERALL PASS ✅\n 27|```\n### SPIKE-03-quic-survival.md\n 20|\n*21|Invariants: **A** QUIC conn alive whole transfer (chunks advance past the kill seq) · **B** chunk counter contiguous (no gap / no premature end) · **C** control from the post-restart brain reaches the broker-owned QUIC sender · **D** every chunk delivered exactly once (no duplicate seq).\n 22|\n 23|## Result\n 24|\n 25|```\n*26|peer chunks 0..125 contiguous, no gap across the brain kill (~seq 58) / restart window\n 27|post-restart MARKER reached the broker-owned QUIC sender | no duplicate seq\n 28|A PASS  B PASS  C PASS  D PASS  →  OVERALL PASS ✅   (4/4 runs, stable)\n 29|```\n### SPIKE-04-forkpty-parity.md\n 15|\n*16|Invariants (same as Spike #1): **A** child survives brain restart (no PTY EOF) · **B** PTY counter contiguous · **C** input reaches child after brain restart · **D** live client stream contiguous across restart.\n 17|\n 18|## Result\n 19|\n 20|```\n*21|child pid survived | pty ticks 0..39 contiguous, no EOF\n 22|post-restart input echoed | client 34 ticks, no gap across the kill/restart window\n 23|A PASS  B PASS  C PASS  D PASS  →  OVERALL PASS ✅   (3/3 runs)\n 24|```\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"crates/xtask, .github, docs, tests","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":47,"fileCount":16,"files":[".github/workflows/release.yml","docs/DEFERRED.md","docs/KNOWN-HAZARDS.md","docs/MSG-IDENTITY-DISPATCH.md","docs/MUSL-RELEASE-HANDOFF.md","docs/NEXT-MILESTONE-BUG-TRIAGE.md","docs/NEXT-MILESTONE-MUSL-TIER-TRIAGE.md","docs/REDISPATCH-TRUTH-TRIAGE.md","docs/RELEASE-RUNBOOK.md","docs/THE-FORKENING-TRIAGE.md","docs/W6-GATE-VERDICT.md","docs/adr/0019-digest-adapter-declared-extractor-session-spanning.md","docs/adr/0036-private-fork-distribution.md","docs/spikes/SPIKE-01-broker-handoff.md","docs/spikes/SPIKE-03-quic-survival.md","docs/spikes/SPIKE-04-forkpty-parity.md"],"fileMatches":[{"path":".github/workflows/release.yml","count":7},{"path":"docs/DEFERRED.md","count":1},{"path":"docs/KNOWN-HAZARDS.md","count":11},{"path":"docs/MSG-IDENTITY-DISPATCH.md","count":1},{"path":"docs/MUSL-RELEASE-HANDOFF.md","count":2},{"path":"docs/NEXT-MILESTONE-BUG-TRIAGE.md","count":1},{"path":"docs/NEXT-MILESTONE-MUSL-TIER-TRIAGE.md","count":1},{"path":"docs/REDISPATCH-TRUTH-TRIAGE.md","count":2},{"path":"docs/RELEASE-RUNBOOK.md","count":11},{"path":"docs/THE-FORKENING-TRIAGE.md","count":1},{"path":"docs/W6-GATE-VERDICT.md","count":1},{"path":"docs/adr/0019-digest-adapter-declared-extractor-session-spanning.md","count":1},{"path":"docs/adr/0036-private-fork-distribution.md","count":1},{"path":"docs/spikes/SPIKE-01-broker-handoff.md","count":2},{"path":"docs/spikes/SPIKE-03-quic-survival.md","count":2},{"path":"docs/spikes/SPIKE-04-forkpty-parity.md","count":2}],"truncated":true,"displayContent":"# .github/workflows/\n## release.yml\n 140│          cargo run -p xtask -- docs-bundle dist/spt-docs.tar.gz\n*141│      - name: Build user-facing release notes from CHANGELOG\n*142│        # The release BODY is the user-facing changelog: the `## [<version>]`\n*143│        # section of CHANGELOG.md (Added / Changed / Fixed, UX items only — no\n 144│        # internal lingo). Authoring that section before tagging is a required\n 145│        # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the\n 146│        # release loudly rather than shipping an empty/boilerplate body — the\n*147│        # changelog is not optional.\n 148│        run: |\n 149│          ver=\"${GITHUB_REF_NAME#v}\"   # v0.1.2 -> 0.1.2\n 150│          notes=\"dist/RELEASE_NOTES.md\"\n    │...\n 156│            grab { print }\n*157│          ' CHANGELOG.md > \"$notes\"\n 158│          if ! grep -q '[^[:space:]]' \"$notes\"; then\n*159│            echo \"::error::CHANGELOG.md has no '## [$ver]' section — add the user-facing changelog for this release before tagging (see docs/RELEASE-RUNBOOK.md).\"\n 160│            exit 1\n 161│          fi\n 162│          echo \"----- release body for ${GITHUB_REF_NAME} -----\"\n    │...\n 177│            --title \"spt ${GITHUB_REF_NAME}\" \\\n*178│            --notes-file dist/RELEASE_NOTES.md \\\n 179│            dist/spt-x86_64-* \\\n 180│            dist/SHA256SUMS \\\n 181│            dist/manifest.schema.json \\\n\n# docs/\n## DEFERRED.md\n 47│| **Cross-node shell TUNNEL relay (REQ-SHELL-4 cross-node-on-LAN carry)**: W3 built + proved the opaque byte tunnel **same-node only** (broker-homed `TunnelHub` loopback stream pair + the local control socket; `tunnel_e2e` round-trips `<EVENT`-looking bytes byte-exact). The **cross-node** path does NOT exist: `StreamFamily` (dispatch.rs) has no `Tunnel` variant, `classify_first_line` no tunnel arm, `SHELL_LINK` carries only relink/cmd/drive (no tunnel action), and every tunnel surface (`tunnel_ensure/sen...\n*48│| **Per-connection contiguous-sent cursor — structural guard against a decreasing-floor controller re-take (`REQ-HAZARD-CONTROLLER-WRITER-REORDER` follow-up)**: v0.13.0 P1c fixes the controller-writer reorder while KEEPING handoff's eager subscribe (fix #1 \"drop the subscribe\" was reverted — it's the standalone-resume mechanism), so the decreasing-floor double-take (handoff subscribe@K + serve_attach subscribe@0) is still PRESENT and made safe by: `controller_writer` epoch-gated under `send.lock()` (...\n 49│| **Adapter-manifest-enabled rc VT/mouse mode (`spt rc` scroll vs right-click-paste xor resolution)**: v0.13.0 P1/P1b's Windows `EnableMouseCapture` (for client-originated right-click bracketed paste, 7.18) makes Windows Terminal hand ALL wheel events to rc instead of scrolling its own scrollback; rc forwards the wheel to the harness as SGR reports (7.20) but ONLY when the harness has mouse-reporting on (`enabled && sgr`) — and **Claude Code's REPL never enables mouse tracking** (it has no internal scr...\n 50│| **Always-on endpoint kind + `#`-sigil addressing (ADR-0023; REQ-EP-8 / REQ-EP-9) — the core seam for adapter-bridged group/forum messaging**: a `[always-on]` manifest section declaring a resident, owner-less, addressable endpoint binary the daemon boot-launches + supervises **continuously** (independent of agent liveness, register-triggered, one per `<adapter>[:profile]`), which self-manages its `#`-addressed channel endpoints via the existing `api bind` and may call the already-ungated `endpoint wak...\n## KNOWN-HAZARDS.md\n  26│- **spt-core mapping:** cross-node Psyche sync (ADR-0002/0003) replaces git-repo sync, so the *git* lock may disappear — but any equivalent lockfile in the new sync mechanism needs the same stale-sweep on boot.\n *27│- **Sister cite:** CHANGELOG v1.11.20 \"Stale `index.lock`\"; `src/common/git.rs`.\n  28│\n  29│### 1.4 Deferred spool rows must not leak to the event stream\n  30│- **Failure:** a hook spools a deferred (spool-only, no TCP wake) notice; startup `drain_all` flushes ALL rows including deferred → event emitted at wrong time/priority.\n    │...\n  48│- **spt-core mapping:** session binding (parent-process-tree anchor) still applies for harness-hosted topology. For spt-hosted sessions the broker holds the child directly → liveness is the broker's held-handle state, more reliable than PID polling.\n *49│- **Sister cite:** `src/live/wrapper/orphan.rs:141-161`; CHANGELOG v1.11.20.\n  50│\n  51│### 2.2 Stdin session_id precedence over env\n  52│- **Failure:** subagent inherits a stale `OWL_SESSION_ID` env across `/clear`; hook gets two session_ids (fresh stdin, stale env) → wrong-agent binding.\n    │...\n  54│- **spt-core mapping:** the harness-contract subcommand surface must define the same precedence for whatever identity fields hooks pass in.\n *55│- **Sister cite:** CHANGELOG v1.35.1 \"IN-05\"; `hook_subagent_start.rs:40-51`.\n  56│\n  57│### 2.3 Binary-handoff argv schema must stay backward-compatible\n  58│- **Failure:** old binary spawns new binary with old argv arity; clap rejects before state rehydration → wrapper dies unlogged.\n    │...\n  60│- **spt-core mapping:** CRITICAL — self-update (ADR-0004) makes handoff routine. The broker↔brain IPC and any brain-relaunch argv must be versioned and forward/backward tolerant (a newer brain talks to an older broker). This is the single most update-frequency-sensitive invariant.\n *61│- **Sister cite:** `src/live/wrapper/lifecycle.rs:17-106`; `src/cli.rs` defaults; CHANGELOG v1.11.10.\n  62│\n  63│### 2.4 Generation `gen_start` always = now() on cold-start AND handoff\n  64│- **Failure:** stale gen_start from a rehydrated state file fires time-based discriminators on the new process.\n    │...\n  89│- **spt-core mapping:** same sweep on daemon (re)start per hosted instance.\n *90│- **Sister cite:** CHANGELOG v1.11.20; `src/owl/cleanup.rs:97`.\n  91│\n  92│### 3.3 Orphan teardown fires echo-commune BEFORE INIT_SIGNOFF\n  93│- **Failure:** teardown delivers INIT_SIGNOFF without first saving the final context delta → Psyche signoff lacks the context-save summary.\n    │...\n 123│- **Invariant:** deferred rows are never flushed by the event-stream drain; only `drain_non_deferred_*` / `peek_all` touch them.\n*124│- **Sister cite:** CHANGELOG v1.11.20; `src/common/spool.rs`. (See also 1.4.)\n 125│\n 126│### 4.5 Inbox legacy compat must not double-deliver\n 127│- **Failure:** message surfaced via both spool (durable) and legacy inbox files → duplicate or racing delivery.\n    │...\n 185│- **spt-core mapping:** all on-disk state writes (registry, trust store, spool checkpoints) use this pattern. Self-update binary swap on Windows especially.\n*186│- **Sister cite:** CHANGELOG \"EBUSY\"; `src/common/owlery.rs` atomic_write.\n 187│\n 188│### 5.3 Git/subprocess timeout stamping\n 189│- **Failure:** a hung subprocess (git on slow net) blocks the supervisor indefinitely.\n    │...\n 292│- **spt-core mapping:** clean greenfield layout from day one (no migration window) — pick one structure, route everything through the registry. Storage layout deferred to design phase but this single-source-of-truth rule is binding.\n*293│- **Sister cite:** `src/common/perch_path.rs`; CHANGELOG Phase 25.4.\n 294│\n 295│### 6.2 Soft-cleanup preserves state, removes `ready`\n 296│- **Failure:** hard-deleting a perch on cleanup loses spool (incl. stored signoff) needed for offline recovery.\n    │...\n 304│- **spt-core mapping:** any destructive maintenance command must check for live child instances first.\n*305│- **Sister cite:** CHANGELOG v1.11.20 Phase 35.1.\n 306│\n 307│### 6.4 Drop files are single-writer (supervisor-owned), read-only for the mind\n 308│- **Failure:** the Psyche LLM deletes a commune drop file the wrapper is concurrently reading → race, lost commune.\n    │...\n 310│- **spt-core mapping:** the daemon is the single writer; the harness-invoked mind never mutates drop files. Bake the ownership into the runtime contract.\n*311│- **Sister cite:** CHANGELOG v1.11.7; `src/live/context.rs:615` (removed delete).\n 312│\n 313│### 6.5 Direct-write precedence guard against stale LLM overwrites\n 314│- **Failure:** the LLM emits an older snapshot that clobbers a fresher direct write to a context file.\n    │...\n 316│- **spt-core mapping:** cross-node Psyche sync (ADR-0003) makes this multi-writer across machines — the precedence marker must include node identity **plus a per-node version vector** (entries from each node's monotonic `EpochSource`; wall-clock never orders). Distributed rule (ADR-0013, M4-D6): dominate→accept, dominated→drop, **concurrent→surface as durable replicated conflict artifacts + Psyche-reconcile on the active instance's node — never silent newest-wins, never lose either version**. T...\n*317│- **Sister cite:** CHANGELOG v1.11.6; `src/owl/echo_commune.rs`.\n 318│\n 319│### 6.6 Surfaced context conflicts preserve both versions until dominated\n 320│- **Failure:** a cross-node concurrent context write (version vectors, neither dominates) gets auto-picked or partially dropped — half a mind silently lost.\n## MSG-IDENTITY-DISPATCH.md\n 93│  doyle for fix-shape ruling BEFORE build.\n*94│- Release: deployah, counter from published metadata, bump rides IN the release PR. CHANGELOG\n 95│  census = field symptoms across all six waves (dup deliveries, seat-theft, lost communes,\n 96│  bringup dead-ends, gateway-down-after-restart, poison log floods).\n 97│\n## MUSL-RELEASE-HANDOFF.md\n  2│\n *3│**W3 gate = PASS 2026-07-08 (doyle).** Branch `feat/musl-w3-release-artifact` @`7d776ea` (off `main`@`047db7d`). This is the milestone-completing wave — the release cut is how the musl artifact reaches the field. Your lane ([[dont-solo-across-role-lines]]): bump + CHANGELOG + release PR + tag + publish + real-node verify.\n  4│\n  5│## What's gated (don't re-gate the code — verify the PUBLISH)\n  6│- release.yml musl matrix (kitsubito, `CC_x86_64_unknown_linux_musl=musl-gcc`, in-job musl-tools+cmake+target); assemble globs `spt-x86_64-*` into SHA256SUMS + upload; xtask signs + update-set-enters musl by registry-derived triple (zero xtask code change post-W1).\n   │...\n 10│1. On the W3 branch: bump VERSION + Cargo.lock first-party ([[release-Cargo.lock-first-party]]); counter from PUBLISHED spt-releases metadata ([[release-counter-from-published]]) — last was v0.29.1=counter 50, so expect **v0.30.0 = counter 51** (minor: additive new platform), CONFIRM from published not memory.\n*11│2. CHANGELOG: user-facing surface = \"static-musl Linux artifact (`spt-x86_64-linux-musl`) — runs on pre-glibc-2.39 hosts; `spt update fetch` now selects+verifies it. gnu stays the default Linux artifact.\" Scope by observable surface ([[changelog-scope-vs-commit-range]]).\n 12│3. Release PR (milestone+release, [[release-standard-bump-in-pr]]), CI both legs green, merge → tag on merge.\n 13│4. Tag fires release.yml — **the FIRST real CI musl build happens here** (the musl matrix runs on tag, not PR). De-risked: I built it clean on kitsubito. Watch the kitsubito musl leg; if it flakes on CI-runner contention, [[seedmap-test-collides-live-daemon]] (kitsubito = musl-build box AND gnu runner) — sequence/sweep, rerun `--failed` (no re-tag).\n 14│5. **Close the field gap for real:** after publish, on a musl node run `spt update fetch` → must now get the musl artifact (NO NoArtifactForPlatform) → verify SHA256+sig over musl bytes → apply → self-updates → self-IDs musl at the new version. THAT is the full self-update E2E completing on the published release (my gate proved every leg upstream of the published artifact).\n## NEXT-MILESTONE-BUG-TRIAGE.md\n 217│## Operator rulings logged\n*218│- **#11:** picker remote-detail + status palette DID ship in v0.17.0 (W5) — CHANGELOG bullet \"endpoint picker shows remote endpoints in full detail\" + the lifecycle \"Reading the picker\" legend; it just didn't spell out \"colored squares.\" #11 here = bring the non-interactive `endpoint list` to that same palette (NOT yet shipped).\n 219│- **#14:** confirmed — switch the top-right ID badge OFF next release; save the concept for a future web SPT GUI. **NON-NEGOTIABLE: accurate PTY representation with zero artifacts** (raises the bar on #6/#7/#8 — a real fix, not just disabling the badge).\n 220│- **#6:** server-side grid approach confirmed correct.\n 221│- **#15:** display-fix path accepted.\n## NEXT-MILESTONE-MUSL-TIER-TRIAGE.md\n 34│release.yml gains a musl matrix entry (build on kitsubito; install `musl-tools`+`cmake`+target in-job, `CC_x86_64_unknown_linux_musl=musl-gcc`); the assemble job includes `spt-x86_64-linux-musl` in SHA256SUMS + the release upload; `release-publish` (xtask) signs the musl artifact; the update-set carries its artifact entry.\n*35│- **Gate (release-pipeline touch → real E2E):** cut a draft/test release with the musl artifact; a **static musl binary on a sub-2.39-glibc box** runs `spt update fetch` → gets the musl artifact (no `NoArtifactForPlatform`), applies, and self-updates. Verify SHA256 + signature over the musl bytes. Bump/CHANGELOG ride the release PR ([[release-standard-bump-in-pr]]); deployah publishes.\n 36│\n 37│## Notes / rulings pre-banked\n 38│- **No source-dependency changes** — feasibility proved the current tree links static musl as-is. Do NOT swap the crypto provider (aws-lc-sys builds clean on musl here); keep the tree identical.\n## REDISPATCH-TRUTH-TRIAGE.md\n  2│\n *3│> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first\n  4│> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:\n  5│> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n  6│> (hertz source-level, doyle seam-verified same day). Decision record:\n   │...\n 41│2/3): 4/5 endpoints frozen across both 2026-07-16 doyle refreshes\n*42│(deployah-recovery + the v0.33.0 apply) + lia on the update;\n 43│`DISPATCH:N:Failed` lines with low/historical stream ids in fresh\n 44│generations = the indiscriminate-replay supporting evidence.\n 45│\n## RELEASE-RUNBOOK.md\n 23│   matching first-party `spt-*` lines in `Cargo.lock` — leave third-party lines\n*24│   alone), the regenerated docs (`cargo run -p xtask -- gen`), and the changelog\n 25│   section (step 2) **in one PR**: the feature PR's final commit, or a dedicated\n 26│   release PR when the work already merged. That PR's CI validates the **exact\n 27│   commit that gets tagged**, so on merge you tag straight away — the green PR run\n   │...\n 31│     to what the feature PR already proved green (only the version string,\n*32│     changelog, and lockfile lines differ); that second pass is ~one CI cycle of\n 33│     pure latency.\n 34│   - Merging the release PR still fires `push:[main]` CI automatically — let it\n 35│     run as a **non-blocking backstop**; it is **not** a tag gate. Tag the merged\n 36│     `main` HEAD as soon as the PR is merged.\n*37│2. **Write the user-facing changelog** — add a `## [0.X.Y] - <date>` section at\n*38│   the top of `CHANGELOG.md` (under the intro) with **Added / Changed / Fixed**\n 39│   subsections. This section becomes the GitHub Release **body** verbatim\n*40│   (`release.yml` extracts it via `--notes-file`), so it is the changelog every\n 41│   spt user reads. Rules:\n 42│   - **spt-user-facing UX only.** What a person running the `spt` CLI notices or\n 43│     does differently. Name the actual commands/flags they type.\n   │...\n 48│   - The release **fails loudly** if the tagged version has no `## [0.X.Y]`\n*49│     section — the changelog is not optional.\n*50│   - **After inserting the new section, assert the version ladder is\n*51│     contiguous**: `grep '^## \\[' CHANGELOG.md` and check the versions descend\n 52│     with no gap. Inserting the new heading with an editor whose anchor spans the\n 53│     previous heading can silently *consume* that heading (v0.34.0 did this to\n*54│     `## [0.33.0]`), folding the prior release's body into the new section — and\n*55│     since the body is published verbatim, the release notes then carry two\n 56│     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n 57│3. **Tag**: `git tag v0.X.Y && git push origin v0.X.Y`. This triggers:\n 58│   - `release.yml` — both runners build, the assemble job extracts this\n*59│     version's `CHANGELOG.md` section into the release body and creates a\n 60│     **draft** release on spt-releases with `spt-x86_64-linux`,\n 61│     `spt-x86_64-windows.exe`, `SHA256SUMS`, `manifest.schema.json`,\n 62│     `mock-adapter.zip`;\n## THE-FORKENING-TRIAGE.md\n 36│  cli.rs:5363; xtask REPO const, xtask main.rs:729;\n*37│  notif.rs CONSENT_CHANGELOG_URL rider).\n 38│- Loud failure tokens: gh missing → `UPDATE_FETCH_REJECTED:GhCliRequired`\n 39│  with OS-specific install hints (winget/apt/brew); gh unauthed → distinct\n 40│  `GhAuthRequired` pointing at `gh auth login`.\n## W6-GATE-VERDICT.md\n 66│\n*67│CHANGELOG census covers v0.28.0..HEAD (the counter-49 cut).\n\n## adr/\n### 0019-digest-adapter-declared-extractor-session-spanning.md\n  8│\n *9│M9 reconciled the digest onto the log source and, in its plan §2, chose \"approach A\": a single published `{role,text,tool}` contract carried in the adapter's `[history]` records, **no digest-specific manifest seam** — the adapter's existing `[history]` normalizer was expected to emit the contract, and the v0.5.0 release notes told adapter authors exactly that. This decision was made inside the milestone and **was not approved by the operator**, who reopened it on review. Four problems are real:\n 10│\n 11│1. **Dual-consumer conflict.** One `[history]` source feeds *two* consumers with opposite needs: the **echo-commune** pipes records *opaque and verbatim* to a summarizer (it wants full-fidelity records), while the **digest** needs them to *parse* as the contract. One normalizer cannot serve both — emit rich records and the digest silently drops every line; emit contract records and echo loses fidelity.\n 12│2. **Silent failure.** `spt_term::projection::record_to_tagged` is `serde_json::from_str(raw).ok()` — any line that is not valid contract JSON is dropped with no count, sample, or log. A normalizer that does not exactly match the contract yields an **empty digest with zero signal**; the author's only symptom is \"`spt endpoint digest` returns nothing.\"\n### 0036-private-fork-distribution.md\n 64│- Version-matched docs replace always-latest docs — an upgrade for agents,\n*65│  a regression only for reading future release notes, which remain in the\n 66│  release entries on the channel.\n\n## spikes/\n### SPIKE-01-broker-handoff.md\n 18│\n*19│Invariants: **A** child alive whole run (no PTY EOF) · **B** `tick N` counter contiguous · **C** input reaches child after brain restart · **D** client stream contiguous across restart.\n 20│\n 21│## Result\n 22│\n 23│```\n*24│child pid 33880 survived | pty ticks 0..39 contiguous, no EOF\n 25│post-restart input echoed | client: 34 ticks, no gap across the brain kill/restart window\n 26│A PASS  B PASS  C PASS  D PASS  →  OVERALL PASS ✅\n 27│```\n### SPIKE-03-quic-survival.md\n 20│\n*21│Invariants: **A** QUIC conn alive whole transfer (chunks advance past the kill seq) · **B** chunk counter contiguous (no gap / no premature end) · **C** control from the post-restart brain reaches the broker-owned QUIC sender · **D** every chunk delivered exactly once (no duplicate seq).\n 22│\n 23│## Result\n 24│\n 25│```\n*26│peer chunks 0..125 contiguous, no gap across the brain kill (~seq 58) / restart window\n 27│post-restart MARKER reached the broker-owned QUIC sender | no duplicate seq\n 28│A PASS  B PASS  C PASS  D PASS  →  OVERALL PASS ✅   (4/4 runs, stable)\n 29│```\n### SPIKE-04-forkpty-parity.md\n 15│\n*16│Invariants (same as Spike #1): **A** child survives brain restart (no PTY EOF) · **B** PTY counter contiguous · **C** input reaches child after brain restart · **D** live client stream contiguous across restart.\n 17│\n 18│## Result\n 19│\n 20│```\n*21│child pid survived | pty ticks 0..39 contiguous, no EOF\n 22│post-restart input echoed | client 34 ticks, no gap across the kill/restart window\n 23│A PASS  B PASS  C PASS  D PASS  →  OVERALL PASS ✅   (3/3 runs)\n 24│```","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784247426560}}
{"type":"message","id":"be206e1b","parentId":"cbf66037","timestamp":"2026-07-17T00:17:06.566Z","message":{"role":"toolResult","toolCallId":"call_witme2GVSm3ROqGWugXY5Brc|fc_099a7567dadef391016a5974826260819ba721e3f95b4a385d","toolName":"grep","content":[{"type":"text","text":"# CHANGELOG.md\n 48|\n*49|- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\n 50|  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\n 51|  A daemon cycle could previously replay already-finished output onto a live\n 52|  remote terminal: old text re-typed itself, control of the session was stolen,\n...\n 113|\n*114|> **Starting the docs server needs a daemon restart.** The docs server and\n*115|> `spt daemon refresh` live in the always-on daemon. `spt update --restart`\n*116|> brings them up via a full daemon restart (your live sessions restart as the\n 117|> daemon comes back). A bare `spt update` swaps the core binary in place and\n 118|> leaves the running daemon untouched, so the new docs server starts on the\n*119|> daemon's next restart.\n 120|\n 121|### Added\n 122|\n...\n 150|\n*151|- `spt update --restart` — the one-step full cycle: fetch, update adapters, then\n*152|  restart the whole daemon onto the new version. Use it when you want\n 153|  everything — core, adapters, and the always-on daemon — brought current in a\n*154|  single command. Your live sessions restart and come back on their own.\n 155|\n*156|- `spt daemon refresh` — restart just the daemon's coordinator in place, with no\n 157|  binary change and without stopping the daemon. Hosted terminals and the\n 158|  network layer keep running. This is the recovery verb for a stuck coordinator\n 159|  that previously needed a full `daemon stop`/`start`, which killed every hosted\n...\n 185|once with the right sender on them, endpoints you save come back on their own\n*186|after a daemon restart, and several ways an agent's identity or saved context\n 187|could be lost are closed.\n 188|\n*189|> **Applying this update needs a daemon restart.** Parts of this release live\n 190|> in the always-on daemon, so `spt update fetch --apply` lands it via a full\n*191|> daemon restart rather than a live swap — your sessions restart as the daemon\n 192|> comes back.\n 193|\n 194|### Added\n...\n 197|  relaunches every saved endpoint (as a fresh session) each time it starts, so\n*198|  your always-on endpoints come back by themselves after a restart instead of\n 199|  staying offline until someone re-runs them. Saving the same endpoint again\n 200|  replaces its saved entry; `spt endpoint run` without `--save` leaves your\n 201|  startup defaults alone.\n...\n 242|\n*243|> **Applying this update needs a daemon restart.** Unlike the recent seamless\n 244|> in-place updates, this fix lives in the always-on daemon, so `spt update fetch\n*245|> --apply` lands it via a full daemon restart rather than a live swap — your\n*246|> sessions restart as the daemon comes back. This is a one-time cost to install\n 247|> the fix.\n 248|\n 249|### Fixed\n...\n 253|  block indefinitely trying to write to it — freezing live terminal sessions with\n*254|  no recovery short of restarting the box. Every write the daemon makes to a\n 255|  session connection is now time-bounded and cancelable: a stuck consumer is cut\n 256|  loose instead of taking the whole daemon down, so your other sessions keep\n 257|  streaming and a fresh `spt rc` resumes cleanly.\n...\n 267|  your live terminal sessions a few seconds after the swap — they stopped streaming and\n*268|  `spt rc` could no longer attach, so the box had to be restarted to recover. (The\n 269|  previous release narrowed this but did not fully close it.) Resuming across the update\n 270|  no longer floods the daemon's internal channel, so your open sessions keep streaming and\n*271|  `spt rc` keeps working straight through the swap — no freeze, no restart. (Because the\n 272|  fix lives in the incoming version, updating *to* this release is what makes your next\n 273|  in-place update seamless.)\n 274|\n...\n 282|- Applying an update in place with `spt update fetch --apply` could freeze or seize\n*283|  control of your live terminal sessions as the daemon restarted — a resumed session\n 284|  could be taken over so that your open session and `spt rc` stopped responding across\n 285|  the swap. Resumed sessions now re-attach as observers instead of taking over the\n 286|  session's controller, so your open sessions and `spt rc` keep working across the\n\n# crates/spt/\n\n## src/\n### cli.rs\n 245|        #[command(subcommand)]\n*246|        action: Option<UpdateCmd>,\n 247|        /// Update the core binary only — skip the adapters leg of the bare\n 248|        /// composite.\n 249|        #[arg(long, short = 'c')]\n...\n 251|        /// The full-cycle form: fetch, update adapters, then finish by\n*252|        /// restarting the daemon onto the new version (`update apply --finish`)\n 253|        /// as the final step — so the whole node, coordinator and live agents,\n*254|        /// runs the new version when it returns. The restart bounces hosted\n 255|        /// sessions (they come back automatically).\n 256|        #[arg(long)]\n*257|        restart: bool,\n 258|    },\n 259|    /// The node-local docs: open them in your browser, or print their URL.\n 260|    ///\n...\n 572|    /// Stop the daemon (service-aware: a managed service is stopped via its\n*573|    /// manager so it does not auto-restart-fight; else a graceful IPC stop).\n 574|    /// Refuses with a warning if it hosts live sessions (they would be killed) —\n 575|    /// pass --force to stop anyway.\n 576|    Stop {\n...\n 584|    Status,\n*585|    /// Restart the daemon's coordinator process in place — no binary change,\n 586|    /// no stop/start. Hosted terminals and the network layer keep running\n 587|    /// untouched; only the coordinator cycles. The recovery verb for a stuck\n 588|    /// coordinator (e.g. endpoint bringup wedged) that previously needed a\n...\n 989|#[derive(Subcommand)]\n*990|enum UpdateCmd {\n 991|    /// Apply the staged, verified self-update now.\n 992|    Apply {\n*993|        /// Finish onto the new version in one step: install it, then restart the\n 994|        /// daemon so both the coordinator and every live agent run the new\n*995|        /// version. Hosted sessions come back automatically — no manual restart.\n 996|        /// Without this flag, install alone leaves the running daemon on the\n*997|        /// previous version until you restart it yourself.\n 998|        #[arg(long)]\n 999|        finish: bool,\n 1000|    },\n...\n 1131|        name: String,\n*1132|        /// Also persist as the startup default (survives daemon restarts).\n 1133|        #[arg(long)]\n 1134|        save: bool,\n 1135|    },\n...\n 1137|    ///\n*1138|    /// Advertising + connecting restart within one pump cadence.\n 1139|    Attach {\n 1140|        /// The held subnet to serve again.\n 1141|        name: String,\n*1142|        /// Also persist as the startup default (survives daemon restarts).\n 1143|        #[arg(long)]\n 1144|        save: bool,\n 1145|    },\n...\n 1441|            core_only,\n*1442|            restart,\n 1443|        } => match action {\n*1444|            Some(UpdateCmd::Apply { finish }) => cmd_update_apply(finish),\n*1445|            Some(UpdateCmd::Fetch {\n 1446|                channel,\n 1447|                tag,\n 1448|                apply,\n...\n 1450|            // [impl->REQ-UPDATE-ADAPTERS-VERB] the alias form; one engine.\n*1451|            Some(UpdateCmd::Adapters { names }) => {\n 1452|                cmd_adapter_update(&spt_store::perch::adapters_dir(), names.as_deref())\n 1453|            }\n 1454|            // [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n*1455|            // [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\n*1456|            None => cmd_update_composite(core_only, restart),\n 1457|        },\n 1458|        // [impl->REQ-INSTALL-BOOTSTRAP-VERB]\n 1459|        Cmd::Install { dir, no_path } => cmd_install(dir, no_path),\n...\n 3878|/// - A managed service is already active ⇒ warn loudly (running inline fights\n*3879|///   it for the socket — the kitsubito 2026-06-08 restart-loop).\n 3880|// [impl->REQ-DAEMON-3]\n 3881|// [impl->REQ-DAEMON-7]\n 3882|// [impl->REQ-CLI-2]\n### rc.rs\n 1257|                // went down and never came back — rc did NOT resurrect it. Name that\n*1258|                // plainly so the operator restarts the daemon rather than guessing.\n 1259|                let _ = writeln!(\n 1260|                    stdout,\n 1261|                    \"\\r\\n[session '{endpoint_id}' lost — the spt daemon is down and \\\n...\n 1290|                 wedged. Detached. Try `spt endpoint list` for its state, or \\\n*1291|                 `spt endpoint run` to restart it]\"\n 1292|            );\n 1293|            Ok(())\n 1294|        }\n...\n 1327|            \"the daemon didn't answer in time while attaching to '{endpoint_id}' — \\\n*1328|             it may be busy or mid-restart. Try `spt rc {endpoint_id}` again; if it \\\n 1329|             keeps happening, `spt endpoint list` shows the endpoint's state\"\n 1330|        )\n 1331|    } else {\n...\n 1337|/// stream, and the op counter that minted it. One per (re)connect — the B-3\n*1338|/// reconnect loop re-mints the WHOLE bundle (a broker restart drops the conn,\n 1339|/// not just the stream; a fresh OpMinter keeps ADR-0034 `rc` op tracing per\n 1340|/// viewport). [impl->REQ-RC-RECONNECT]\n 1341|struct Established {\n...\n 1453|    // A-4b (REQ-OPID-TRACING-RETRY): the attach stream-open is a tracing-only op.\n*1454|    // On the broker-restart no-longer-held reply, re-mint a FRESH `rc` op and open\n 1455|    // ONCE more. The first attempt uses the conn already established above (happy\n*1456|    // path byte-identical); the one retry RE-DIALS inside `run` — a broker restart\n 1457|    // drops the CONN too, not just the stream, so opening against the dead conn\n 1458|    // would surface a different error instead of self-healing. A 2nd no-longer-held\n 1459|    // surfaces the helper's F-1 public string via the map_err below (never op lingo).\n...\n 1603|                // may still be coming back. DaemonDown included: rc waits for the\n*1604|                // operator to restart it, it does NOT auto-start one.\n 1605|                Err(_) => std::thread::sleep(RECONNECT_PAUSE),\n 1606|            }\n 1607|        };\n...\n 1634|/// Generous: a daemon bounce (`spt daemon stop` + the next auto-start) and a\n*1635|/// node's broker restart both land well inside it.\n 1636|const RECONNECT_WINDOW: Duration = Duration::from_secs(30);\n 1637|/// The pause between reconnect attempts (each attempt already carries its own\n 1638|/// bounded setup deadlines — this just keeps the loop from hammering).\n...\n 1847|    }\n*1848|    // Dedup cursor: a post-restart re-serve may re-transmit `Output` records;\n 1849|    // drop any `seq` already rendered (the operator render cursor, matching the\n 1850|    // local `OutputLog` replay discipline).\n 1851|    let mut cursor = 0u64;\n...\n 2523|        assert!(down.contains(\"won't auto-start\"), \"states rc won't resurrect it\");\n*2524|        assert!(down.contains(\"spt daemon start\"), \"points at the restart\");\n 2525|        let generic = render(\"the connection was severed\", false);\n 2526|        assert!(!generic.contains(\"won't auto-start\"), \"generic omits the daemon-down line\");\n 2527|        assert!(generic.contains(\"didn't succeed\"), \"keeps the didn't-reconnect copy\");\n### wansend.rs\n 359|    // pre-minted epoch, then a genuinely FRESH cli epoch on the one retry the\n*360|    // broker-restart no-longer-held reply triggers. CLI-originated rest op —\n 361|    // cli-ops.json is the counter (ADR-0034 `cli` tag).\n 362|    let mut minted_first = false;\n 363|    let mint = move || -> std::io::Result<MintedOp> {\n...\n 428|    // A-4b (REQ-OPID-TRACING-RETRY): wrap the rest round-trip in the tracing-only\n*429|    // one-shot retry. The re-dial rides INSIDE `run` — a broker restart drops the\n 430|    // CONN too, not just the stream, so the retry must open a fresh conn. `op_id`\n 431|    // (tracing correlation) is intentionally stable across the retry; only the\n 432|    // journal open-op is re-minted. A 2nd no-longer-held surfaces the helper's F-1\n\n### api/\n#### mod.rs\n 76|        /// Bind from this session id when the ephemeral seed is gone (a session\n*77|        /// going live late, or after a daemon restart). With no live seed and\n 78|        /// no session id, listen refuses (NO_SEED).\n 79|        #[arg(long)]\n 80|        session_id: Option<String>,\n#### startup.rs\n 80|             (a stale pre-0.9.0 broker cannot read the new seed format) — run \\\n*81|             `spt daemon stop`, then retry (the broker restarts on the next \\\n 82|             `spt api` call)\"\n 83|        )\n 84|    } else {\n...\n 207|/// Bind WITHOUT a seed, from an explicit session id (F-034 leg c): a session\n*208|/// that goes live LATE (hours after SessionStart, or after a daemon restart)\n 209|/// finds its ephemeral seed gone — nothing re-fires it until the NEXT\n 210|/// SessionStart — so `listen --session-id <sid>` binds directly.\n 211|///\n...\n 588|        Ok(t) => t,\n*589|        // Late go-live / post-daemon-restart (F-034 leg c): the ephemeral seed\n 590|        // is gone and nothing re-fires it until the NEXT SessionStart, so an\n 591|        // explicit --session-id binds directly. NO_SEED only — any OTHER\n 592|        // refusal keeps its own diagnostic (the sid fallback must never paper\n...\n 630|    // shellwake CLI-flips-status / brain-reconciles pattern). `status` is\n*631|    // daemon-authoritative (CONTEXT:194). A new `listen` against a not-yet-restarted\n 632|    // OLD brain leaves the perch online-but-unhosted; that self-heals at the brain's\n 633|    // next BOOT reconcile (livehost reconciles at boot, not only on tick) — the\n 634|    // agent + the message relay below are live throughout regardless.\n\n## tests/\n### brain_split.rs\n 95|/// D3-1 (ADR-0018 Q2) — the seed-control lock + liveness is the broker's, so it\n*96|/// is the **always-up anchor**: a brain restart must never perturb it. The\n 97|/// single-daemon-per-`SPT_HOME` ping (`is_running`, what `ensure_running` /\n 98|/// `daemon stop` target) lives in the broker's foreground loop\n 99|/// (`daemon.rs::Daemon::run`), NOT the brain — so killing the brain and watching\n...\n 128|    // Kill the brain; while the supervisor respawns it, the seed anchor (in the\n*129|    // never-restarting broker) must stay continuously up. Sample across the whole\n 130|    // respawn gap — a single not-running read is a failure (the anchor flapped).\n 131|    kill_pid(pid1);\n 132|    let deadline = Instant::now() + Duration::from_secs(30);\n### brain_survive.rs\n 2|//! swap**: a hosted PTY child + a live QUIC connection survive a brain-**PROCESS**\n*3|//! restart that lands on a **swapped on-disk binary**, with the broker holding\n 4|//! both endpoints untouched throughout. This is the `int` E2E that re-points the\n 5|//! regression-masked REQ-DAEMON-2 / REQ-UPD-3 evidence off the in-process\n 6|//! `brain_swap.rs` shape onto a real process boundary.\n...\n 18|//!   a live QUIC conn). The broker is modeled **in-process** — faithful, not a\n*19|//!   weakening: the broker is the never-restarting anchor *by design*, so an\n 20|//!   in-process [`Broker`] runs the **same Broker code holding the same\n 21|//!   resources** the real broker would across a brain swap. The claim under test\n 22|//!   is the brain process + the swapped binary + broker-held endpoint survival —\n...\n 68|use spt_daemon::brain::Brain;\n*69|use spt_daemon::brainproc::{supervise_brain, BrainRestart, NoTrialEnv, StartReason};\n 70|use spt_daemon::codec::write_frame;\n 71|use spt_daemon::endpoint::broker_socket_name;\n 72|use spt_daemon::frame::{Envelope, Role};\n...\n 189|#[test]\n*190|fn pty_and_quic_survive_brain_process_restart_onto_swapped_binary() {\n 191|    // The in-process broker + brain children both resolve `broker_socket_name()`\n 192|    // from SPT_HOME, so the supervised child connects to OUR broker. (The\n 193|    // established daemon-test pattern — daemon_e2e.rs et al. set it the same way.)\n...\n 254|    //    pre-D6 respawn loop (no readiness gate); the swap is driven by the\n*255|    //    planned-restart signal, exactly as a real `apply` drives it (D3-3). ──\n 256|    let ready_path = home.path().join(\"brain.ready\");\n 257|    let stop = Arc::new(AtomicBool::new(false));\n*258|    let restart = Arc::new(BrainRestart::new());\n 259|    let selected = Arc::new(Mutex::new(fixture_a.clone()));\n 260|    let home_path = home.path().to_path_buf();\n 261|\n...\n 263|        let stop = Arc::clone(&stop);\n*264|        let restart = Arc::clone(&restart);\n 265|        let selected = Arc::clone(&selected);\n 266|        thread::spawn(move || {\n 267|            supervise_brain(\n 268|                &stop,\n*269|                &restart,\n 270|                Duration::from_millis(50),\n 271|                &NoTrialEnv,\n 272|                Duration::from_secs(30),\n...\n 308|\n*309|    // ── SWAP: flip the selected binary to B, then trigger a planned restart\n 310|    //    (what `apply` does: swap on disk, signal the brain to cycle). ──\n 311|    *selected.lock().unwrap() = fixture_b.clone();\n*312|    restart.request();\n 313|\n 314|    // ── Generation 1: the supervisor respawned the brain PROCESS onto the\n 315|    //    SWAPPED binary. New pid, exe_hash == B != A (NEW BYTES RAN). ──\n### daemon_refresh_e2e.rs\n 197|\n*198|    // The broker process must be the SAME OS process (never restarted).\n 199|    let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);\n 200|\n 201|    // The hosted endpoint survives: harness alive + perch back/still ONLINE.\n### endpoint_autostart_e2e.rs\n*1|//! F-038 W5 — REQ-ENDPOINT-AUTOSTART (restart-brings-it-up acceptance).\n 2|//!\n 3|//! The gap this locks down (autostart.rs header, deployah field-confirm\n 4|//! 2026-07-10): a gateway-class endpoint is infra — the phone treats `mobile-gw`\n...\n 8|//! runs on a boot background thread (`replay_startup_endpoints`, after broker\n*9|//! bind) so a restart brings the endpoint back up on its own — a FRESH session\n 10|//! (shape (a): a startup default, not a session restore).\n 11|//!\n 12|//! This gate proves the FIX end to end by driving the REAL `spt` binary across a\n*13|//! full daemon RESTART, all inside a fully SPT_HOME-isolated home (its own\n 14|//! daemon, never the operator's live one — the idle_edge_drain_e2e isolation\n 15|//! precedent):\n 16|//!   1. a REAL daemon (A) hosts a REAL spt-hosted `cc` endpoint;\n...\n 59|/// anywhere in the body still tears the isolated daemon/harness/psyche tree down\n*60|/// (the restart flow has many more panic points than a single-daemon gate).\n 61|struct Reaper {\n 62|    pids: Mutex<Vec<u32>>,\n 63|}\n...\n 149|#[test]\n*150|fn saved_endpoint_replays_on_daemon_restart() {\n 151|    let reaper = Reaper::new();\n 152|\n 153|    // ── (1) Isolated, NET-LESS SPT_HOME. ──\n...\n 259|    // the fresh replay must supersede), then reap the run-A harness + psyche so\n*260|    // the restart brings a genuinely NEW session up, not a survivor.\n 261|    let rec_a = spt_store::info::read_info(&self_perch);\n 262|    let session_a = rec_a.as_ref().map(|r| r.session_id.clone());\n 263|    if let Some(pid) = spt_store::info::read_pid(&self_perch) {\n...\n 287|\n*288|    // ── (6) Daemon B — the RESTART. NO `endpoint run`. Its boot replay thread\n 289|    //    (after broker bind) must bring `gwauto` back up on its own. ──\n 290|    let daemon_b_log = home.path().join(\"daemon-b.stderr.log\");\n 291|    let log_b = std::fs::File::create(&daemon_b_log).expect(\"create daemon B log\");\n...\n 322|        // Done once the loud token is present AND the perch carries a fresh\n*323|        // (post-restart) session id distinct from run A's.\n 324|        let fresh = matches!((&session_a, &session_b), (Some(a), Some(b)) if !b.is_empty() && a != b);\n 325|        if autostart_line.is_some() && fresh {\n 326|            break;\n...\n 342|    eprintln!(\n*343|        \"=== F-038 W5 AUTOSTART-ON-RESTART GATE: online_a={online_a} run_ok={} \\\n 344|         saved_token={} session_a={session_a:?} session_b={session_b:?} \\\n 345|         autostart_line={autostart_line:?} ===\\n=== endpoint run stderr ===\\n{run_stderr}\\n\\\n 346|         === daemon.json after --save ===\\n{cfg_after_save}\\n\\\n...\n 373|        online_a,\n*374|        \"PRECONDITION: the cc endpoint must bind ONLINE under daemon A before restart\"\n 375|    );\n 376|    // (a) The save fired loud AND landed in daemon.json.\n 377|    assert!(\n...\n 387|        brain_b.is_some(),\n*388|        \"PRECONDITION: daemon B (the restart) must come up with a fresh brain.\\n\\\n 389|         === daemon B stderr ===\\n{daemon_b_stderr}\"\n 390|    );\n*391|    // (b) THE KEYSTONE: the restart's boot replay brought the saved endpoint back\n 392|    //     up on its own — the loud ENDPOINT_AUTOSTART:<id> token is present in\n 393|    //     daemon B's stderr, emitted only on a successful launch_harness_brokered_in.\n 394|    let line = match autostart_line {\n...\n 406|    // (c) A replay is a FRESH session (shape (a): a startup default, not a\n*407|    //     restore) — the post-restart perch must carry a session id distinct from\n 408|    //     run A's.\n 409|    if let (Some(a), Some(b)) = (&session_a, &session_b) {\n 410|        assert!(\n### listen_seed_retry_e2e.rs\n 10|//! 2. **Session-id fallback** (REQ-LISTEN-SESSION-ID-FALLBACK): a session whose\n*11|//!    ephemeral seed is GONE (late go-live / post-daemon-restart) binds via\n 12|//!    `api listen <id> --session-id <sid>` — the loud SID_BIND marker path.\n 13|//!\n 14|//! Both drive the real binary with an explicit `--adapter mock` (bin-only crate:\n...\n 166|// [int->REQ-LISTEN-SESSION-ID-FALLBACK] F-034 leg c: a session with NO live seed\n*167|// (its ephemeral SessionStart seed expired / a daemon restart dropped it) binds via\n 168|// `api listen <id> --session-id <sid>`. The seed path yields NO_SEED, the fallback\n 169|// binds from the sid (loud SID_BIND marker), and the perch carries the sid. This is\n*170|// the post-daemon-restart shape — the seed is simply absent.\n 171|#[test]\n 172|fn no_seed_session_binds_via_session_id_fallback() {\n 173|    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());\n### live_adapt_translation_swap_e2e.rs\n 6|//! `Brain::adapter_apply` → stop the resident translation child (frees the OS lock)\n*7|//! → CRC-swap the install dir → restart the translation from the new manifest path\n 8|//! — and the previously-LOCKED install-dir binary is overwritten with the NEW bytes\n*9|//! WHILE the endpoint/session keeps running untouched (brain-parity, no restart).\n 10|//!\n 11|//! Real `spt daemon run` broker + supervised brain + a REAL dummy-harness\n 12|//! `[session.self]` session + a REAL translation binary (`translate_proof_fixture`,\n...\n 108|#[test]\n*109|fn adapter_apply_swaps_locked_translation_binary_without_restarting_endpoint() {\n 110|    // Serialize against the sibling tests — shared process-global SPT_HOME.\n 111|    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());\n 112|    let now_ms = || {\n...\n 240|    //    This proves the binary IS locked — the exact condition the daemon-coordinated\n*241|    //    apply must work around (stop child → swap → restart). On Unix this won't lock,\n 242|    //    so we skip the pre-assert and just proceed to the apply. ──\n 243|    #[cfg(windows)]\n 244|    let direct_overwrite_locked = {\n...\n 286|    //    broker stops the resident translation child (frees the lock), CRC-swaps the\n*287|    //    install dir from staging, restarts the translation from the new path. ──\n 288|    let mut brain = Brain::cold_start(&broker_socket_name(), now_ms())\n 289|        .expect(\"connect a Brain to the live broker for adapter_apply\");\n 290|    let apply_result = brain.adapter_apply(&AdapterApplyReq {\n...\n 299|    //        LANDED on the previously-locked binary);\n*300|    //    (b) the session/endpoint is STILL alive (no restart — brain-parity): the\n 301|    //        harness pid is alive AND the perch is still ONLINE. ──\n 302|    let hash_after = file_hash(&xlate_install);\n 303|    let swap_landed = hash_after.is_some()\n...\n 377|        \"REQ-ADAPTER-LIVE-UPDATE: Brain::adapter_apply must return Ok (the broker stopped \\\n*378|         the resident translation child, CRC-swapped the install dir, and restarted the \\\n 379|         translation) — got {:?}.\\n=== brain stderr ===\\n{brain_stderr}\",\n 380|        apply_result.err().map(|e| e.to_string())\n 381|    );\n...\n 392|\n*393|    // Brain-parity: the endpoint NEVER restarted across the live update.\n 394|    assert!(\n 395|        harness_alive_after,\n 396|        \"REQ-ADAPTER-LIVE-UPDATE (brain-parity): the harness session must STILL be ALIVE \\\n 397|         after the live update — only the translation child cycles, the endpoint never \\\n*398|         restarts. harness_pid={harness_pid:?} was dead post-apply.\\n\\\n 399|         === brain stderr ===\\n{brain_stderr}\"\n 400|    );\n 401|    assert!(\n...\n 792|    );\n*793|    // Brain-parity: the composite endpoint never restarted across the live update.\n 794|    assert!(\n 795|        harness_alive_after,\n 796|        \"REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP (brain-parity): the cc:prof harness must STILL be \\\n### projindex_writer_e2e.rs\n 7|//! daemon start (index published from nothing, batched counters on the status\n*8|//! surface) → daemon restart (warm start: unchanged generation performs NO\n 9|//! scan, the persisted snapshot is byte-untouched) → the five invalidation\n 10|//! event classes fired through their PRODUCTION funnels (the store's own\n 11|//! nudges for context/rename/fork/purge via the real CLI verbs / ContextStore\n...\n 209|\n*210|    // ── (3) WARM START: restart the daemon over the unchanged store. ──\n 211|    let _ = {\n 212|        let mut cmd = Command::new(&spt_bin);\n 213|        cmd.no_window()\n...\n 273|        bytes_before,\n*274|        \"the unchanged snapshot is byte-untouched across the restart\"\n 275|    );\n 276|\n 277|    // ── (4) EVENT INVALIDATIONS against the LIVE daemon. ──\n### resume_no_control_steal_e2e.rs\n 97|\n*98|use spt_daemon::brainproc::{ready_generation_at, supervise_brain, BrainRestart, StartReason, TrialEnv};\n 99|use spt_daemon::codec::{read_frame, write_frame};\n 100|use spt_daemon::endpoint::broker_socket_name;\n 101|use spt_daemon::frame::{Envelope, Role};\n...\n 399|        thread::spawn(move || {\n*400|            let restart = BrainRestart::new();\n 401|            supervise_brain(\n 402|                &stop_sup,\n*403|                &restart,\n 404|                Duration::from_millis(50),\n 405|                env_sup.as_ref(),\n 406|                Duration::from_secs(60),\n### trial_drain_drive_e2e.rs\n 23|//! candidate DRIVES the live older broker's already-shipped reap and un-strands its\n*24|//! OWN trial with no coordinated broker restart.\n 25|//!\n 26|//! ## Why the wedge is established AFTER the candidate boots (the resume paradox)\n 27|//!\n...\n 108|use spt_daemon::brain::Brain;\n*109|use spt_daemon::brainproc::{ready_generation_at, supervise_brain, BrainRestart, StartReason, TrialEnv};\n 110|use spt_daemon::codec::{read_frame, write_frame};\n 111|use spt_daemon::endpoint::broker_socket_name;\n 112|use spt_daemon::frame::{Envelope, Role};\n...\n 373|        thread::spawn(move || {\n*374|            let restart = BrainRestart::new();\n 375|            supervise_brain(\n 376|                &stop_sup,\n*377|                &restart,\n 378|                Duration::from_millis(50),\n 379|                env_sup.as_ref(),\n 380|                Duration::from_secs(60), // generous window — covers a slow-CI boot (eaten before the\n### twohost_cli.rs\n 31|//!    honest both-sockets death: seed + broker die with the process) → A's\n*32|//!    child paints the reconnect banner → B's daemon restarts and the ticker\n 33|//!    respawns → the viewport reheals on the production loop.\n 34|//!\n 35|//! ## Rendezvous (differs from the ladder — deliberately)\n...\n 695|    daemon.stop();\n*696|    println!(\"GATED role B: daemon DOWN (honest stop) — restarting\");\n 697|    let daemon2 = DaemonGuard::start(home.path(), &spt, \"b2\");\n 698|    // Re-anchor the ticker perch on generation 2's pid BEFORE anything\n 699|    // re-advertises: the old anchor died with generation 1, and a dead-pid row\n\n# docs-site/src/\n\n## cli/\n### reference.md\n 421|  stop     Stop the daemon (service-aware: a managed service is stopped via its manager so it does\n*422|           not auto-restart-fight; else a graceful IPC stop). Refuses with a warning if it hosts\n 423|           live sessions (they would be killed) — pass --force to stop anyway\n 424|  status   Node status: daemon state, member subnets, local endpoints (the bare spt daemon view)\n*425|  refresh  Restart the daemon's coordinator process in place — no binary change, no stop/start.\n 426|           Hosted terminals and the network layer keep running untouched; only the coordinator\n 427|           cycles. The recovery verb for a stuck coordinator (e.g. endpoint bringup wedged) that\n 428|           previously needed a full daemon stop/start killing every hosted session\n...\n 475|Stop the daemon (service-aware: a managed service is stopped via its manager so it does not\n*476|auto-restart-fight; else a graceful IPC stop). Refuses with a warning if it hosts live sessions\n 477|(they would be killed) — pass --force to stop anyway\n 478|\n 479|Usage: spt daemon stop [OPTIONS]\n...\n 506|```text\n*507|Restart the daemon's coordinator process in place — no binary change, no stop/start. Hosted\n 508|terminals and the network layer keep running untouched; only the coordinator cycles. The recovery\n 509|verb for a stuck coordinator (e.g. endpoint bringup wedged) that previously needed a full `daemon\n 510|stop/start` killing every hosted session\n...\n 1011|      --save\n*1012|          Also persist as the startup default (survives daemon restarts)\n 1013|\n 1014|  -h, --help\n 1015|          Print help (see a summary with '-h')\n...\n 1022|\n*1023|Advertising + connecting restart within one pump cadence.\n 1024|\n 1025|Usage: spt subnet attach [OPTIONS] <NAME>\n 1026|\n...\n 1037|      --save\n*1038|          Also persist as the startup default (survives daemon restarts)\n 1039|\n 1040|  -h, --help\n 1041|          Print help (see a summary with '-h')\n...\n 1107|\n*1108|      --restart\n*1109|          The full-cycle form: fetch, update adapters, then finish by restarting the daemon onto the\n*1110|          new version (update apply --finish) as the final step — so the whole node, coordinator\n*1111|          and live agents, runs the new version when it returns. The restart bounces hosted sessions\n 1112|          (they come back automatically)\n 1113|\n 1114|  -h, --help\n...\n 1125|Options:\n*1126|      --finish  Finish onto the new version in one step: install it, then restart the daemon so both\n 1127|                the coordinator and every live agent run the new version. Hosted sessions come back\n*1128|                automatically — no manual restart. Without this flag, install alone leaves the\n*1129|                running daemon on the previous version until you restart it yourself\n 1130|      --json    Emit machine-readable JSON instead of the human view. Honored by the read/status\n 1131|                commands (list, whoami, status, description, role, the *-list queries, how-to);\n 1132|                action commands ignore it\n...\n 1298|      --session-id <SESSION_ID>  Bind from this session id when the ephemeral seed is gone (a\n*1299|                                 session going live late, or after a daemon restart). With no live\n 1300|                                 seed and no session id, listen refuses (NO_SEED)\n 1301|  -h, --help                     Print help\n 1302|```\n\n## concepts/\n### overview.md\n 62|  processes, listening sockets. It almost never updates.\n*63|- the **brain** holds all logic and restarts freely. An update swaps the\n 64|  brain while the broker keeps every session's process and byte stream\n 65|  intact — running agents don't notice.\n 66|\n\n## harness-contract/\n### api.md\n 98|and survives until exactly one of: a successful `listen` bind consumes it, a\n*99|newer `seed` for the same pid overwrites it, or the daemon process restarts\n 100|(which drops the whole map). Nothing re-fires it until the harness's **next**\n 101|SessionStart. So an adapter must not rely on the seed for a session that goes\n*102|live late (hours after SessionStart) or after a daemon restart — that is what\n 103|`listen --session-id` (below) is for.\n 104|\n 105|### `api listen <id> [--once] [--parent-pid <pid>] [--subnet <name>] [--session-id <sid>]`\n...\n 123|**`--session-id <sid>` — binding when the seed is gone.** A session that goes\n*124|live late, or after a daemon restart, finds no live seed; with `--session-id`\n 125|the listener binds directly from the given harness session id (loud\n 126|`SID_BIND:<id>` marker). The fallback fires **only** on `NO_SEED` — every\n 127|other refusal keeps its own diagnostic — and carries the same identity/auth\n\n## instances/\n### overview.md\n 32|Infrastructure endpoints (a gateway the phone treats as always-there) should\n*33|not need hands-on bringup after a box reboot or daemon restart. `spt endpoint\n 34|run … --save` persists the run — endpoint id, adapter option, and working\n 35|directory — as a **startup default** in `daemon.json`; the daemon **replays**\n 36|every saved default when it starts, as a fresh session with the adapter\n...\n 45|This is a **startup default**, not a session restore — it replays what you\n*46|saved, never \"whatever was up before the restart\" — and it is symmetric with\n 47|`spt subnet attach --save` (the serve-state startup default).\n 48|\n 49|## Commands\n\nShowing files 1-20 of 24+. Use skip=20 for the next page, or narrow paths/pattern."}],"details":{"scopePath":"crates/spt/src, crates/spt/tests, CHANGELOG.md, docs-site, CONTEXT.md","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":129,"fileCount":20,"files":["crates/spt/src/api/mod.rs","crates/spt/src/api/startup.rs","crates/spt/src/cli.rs","crates/spt/src/rc.rs","crates/spt/src/wansend.rs","crates/spt/tests/brain_split.rs","crates/spt/tests/brain_survive.rs","crates/spt/tests/daemon_refresh_e2e.rs","crates/spt/tests/endpoint_autostart_e2e.rs","crates/spt/tests/listen_seed_retry_e2e.rs","crates/spt/tests/live_adapt_translation_swap_e2e.rs","crates/spt/tests/projindex_writer_e2e.rs","crates/spt/tests/resume_no_control_steal_e2e.rs","crates/spt/tests/trial_drain_drive_e2e.rs","crates/spt/tests/twohost_cli.rs","CHANGELOG.md","docs-site/src/cli/reference.md","docs-site/src/concepts/overview.md","docs-site/src/harness-contract/api.md","docs-site/src/instances/overview.md"],"fileMatches":[{"path":"crates/spt/src/api/mod.rs","count":1},{"path":"crates/spt/src/api/startup.rs","count":4},{"path":"crates/spt/src/cli.rs","count":20},{"path":"crates/spt/src/rc.rs","count":10},{"path":"crates/spt/src/wansend.rs","count":2},{"path":"crates/spt/tests/brain_split.rs","count":2},{"path":"crates/spt/tests/brain_survive.rs","count":10},{"path":"crates/spt/tests/daemon_refresh_e2e.rs","count":1},{"path":"crates/spt/tests/endpoint_autostart_e2e.rs","count":13},{"path":"crates/spt/tests/listen_seed_retry_e2e.rs","count":3},{"path":"crates/spt/tests/live_adapt_translation_swap_e2e.rs","count":10},{"path":"crates/spt/tests/projindex_writer_e2e.rs","count":3},{"path":"crates/spt/tests/resume_no_control_steal_e2e.rs","count":3},{"path":"crates/spt/tests/trial_drain_drive_e2e.rs","count":4},{"path":"crates/spt/tests/twohost_cli.rs","count":2},{"path":"CHANGELOG.md","count":20},{"path":"docs-site/src/cli/reference.md","count":15},{"path":"docs-site/src/concepts/overview.md","count":1},{"path":"docs-site/src/harness-contract/api.md","count":3},{"path":"docs-site/src/instances/overview.md","count":2}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# CHANGELOG.md\n  48│\n *49│- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\n  50│  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\n  51│  A daemon cycle could previously replay already-finished output onto a live\n  52│  remote terminal: old text re-typed itself, control of the session was stolen,\n    │...\n 113│\n*114│> **Starting the docs server needs a daemon restart.** The docs server and\n*115│> `spt daemon refresh` live in the always-on daemon. `spt update --restart`\n*116│> brings them up via a full daemon restart (your live sessions restart as the\n 117│> daemon comes back). A bare `spt update` swaps the core binary in place and\n 118│> leaves the running daemon untouched, so the new docs server starts on the\n*119│> daemon's next restart.\n 120│\n 121│### Added\n 122│\n    │...\n 150│\n*151│- `spt update --restart` — the one-step full cycle: fetch, update adapters, then\n*152│  restart the whole daemon onto the new version. Use it when you want\n 153│  everything — core, adapters, and the always-on daemon — brought current in a\n*154│  single command. Your live sessions restart and come back on their own.\n 155│\n*156│- `spt daemon refresh` — restart just the daemon's coordinator in place, with no\n 157│  binary change and without stopping the daemon. Hosted terminals and the\n 158│  network layer keep running. This is the recovery verb for a stuck coordinator\n 159│  that previously needed a full `daemon stop`/`start`, which killed every hosted\n    │...\n 185│once with the right sender on them, endpoints you save come back on their own\n*186│after a daemon restart, and several ways an agent's identity or saved context\n 187│could be lost are closed.\n 188│\n*189│> **Applying this update needs a daemon restart.** Parts of this release live\n 190│> in the always-on daemon, so `spt update fetch --apply` lands it via a full\n*191│> daemon restart rather than a live swap — your sessions restart as the daemon\n 192│> comes back.\n 193│\n 194│### Added\n    │...\n 197│  relaunches every saved endpoint (as a fresh session) each time it starts, so\n*198│  your always-on endpoints come back by themselves after a restart instead of\n 199│  staying offline until someone re-runs them. Saving the same endpoint again\n 200│  replaces its saved entry; `spt endpoint run` without `--save` leaves your\n 201│  startup defaults alone.\n    │...\n 242│\n*243│> **Applying this update needs a daemon restart.** Unlike the recent seamless\n 244│> in-place updates, this fix lives in the always-on daemon, so `spt update fetch\n*245│> --apply` lands it via a full daemon restart rather than a live swap — your\n*246│> sessions restart as the daemon comes back. This is a one-time cost to install\n 247│> the fix.\n 248│\n 249│### Fixed\n    │...\n 253│  block indefinitely trying to write to it — freezing live terminal sessions with\n*254│  no recovery short of restarting the box. Every write the daemon makes to a\n 255│  session connection is now time-bounded and cancelable: a stuck consumer is cut\n 256│  loose instead of taking the whole daemon down, so your other sessions keep\n 257│  streaming and a fresh `spt rc` resumes cleanly.\n    │...\n 267│  your live terminal sessions a few seconds after the swap — they stopped streaming and\n*268│  `spt rc` could no longer attach, so the box had to be restarted to recover. (The\n 269│  previous release narrowed this but did not fully close it.) Resuming across the update\n 270│  no longer floods the daemon's internal channel, so your open sessions keep streaming and\n*271│  `spt rc` keeps working straight through the swap — no freeze, no restart. (Because the\n 272│  fix lives in the incoming version, updating *to* this release is what makes your next\n 273│  in-place update seamless.)\n 274│\n    │...\n 282│- Applying an update in place with `spt update fetch --apply` could freeze or seize\n*283│  control of your live terminal sessions as the daemon restarted — a resumed session\n 284│  could be taken over so that your open session and `spt rc` stopped responding across\n 285│  the swap. Resumed sessions now re-attach as observers instead of taking over the\n 286│  session's controller, so your open sessions and `spt rc` keep working across the\n\n# crates/spt/\n\n## src/\n### cli.rs\n  245│        #[command(subcommand)]\n *246│        action: Option<UpdateCmd>,\n  247│        /// Update the core binary only — skip the adapters leg of the bare\n  248│        /// composite.\n  249│        #[arg(long, short = 'c')]\n     │...\n  251│        /// The full-cycle form: fetch, update adapters, then finish by\n *252│        /// restarting the daemon onto the new version (`update apply --finish`)\n  253│        /// as the final step — so the whole node, coordinator and live agents,\n *254│        /// runs the new version when it returns. The restart bounces hosted\n  255│        /// sessions (they come back automatically).\n  256│        #[arg(long)]\n *257│        restart: bool,\n  258│    },\n  259│    /// The node-local docs: open them in your browser, or print their URL.\n  260│    ///\n     │...\n  572│    /// Stop the daemon (service-aware: a managed service is stopped via its\n *573│    /// manager so it does not auto-restart-fight; else a graceful IPC stop).\n  574│    /// Refuses with a warning if it hosts live sessions (they would be killed) —\n  575│    /// pass --force to stop anyway.\n  576│    Stop {\n     │...\n  584│    Status,\n *585│    /// Restart the daemon's coordinator process in place — no binary change,\n  586│    /// no stop/start. Hosted terminals and the network layer keep running\n  587│    /// untouched; only the coordinator cycles. The recovery verb for a stuck\n  588│    /// coordinator (e.g. endpoint bringup wedged) that previously needed a\n     │...\n  989│#[derive(Subcommand)]\n *990│enum UpdateCmd {\n  991│    /// Apply the staged, verified self-update now.\n  992│    Apply {\n *993│        /// Finish onto the new version in one step: install it, then restart the\n  994│        /// daemon so both the coordinator and every live agent run the new\n *995│        /// version. Hosted sessions come back automatically — no manual restart.\n  996│        /// Without this flag, install alone leaves the running daemon on the\n *997│        /// previous version until you restart it yourself.\n  998│        #[arg(long)]\n  999│        finish: bool,\n 1000│    },\n     │...\n 1131│        name: String,\n*1132│        /// Also persist as the startup default (survives daemon restarts).\n 1133│        #[arg(long)]\n 1134│        save: bool,\n 1135│    },\n     │...\n 1137│    ///\n*1138│    /// Advertising + connecting restart within one pump cadence.\n 1139│    Attach {\n 1140│        /// The held subnet to serve again.\n 1141│        name: String,\n*1142│        /// Also persist as the startup default (survives daemon restarts).\n 1143│        #[arg(long)]\n 1144│        save: bool,\n 1145│    },\n     │...\n 1441│            core_only,\n*1442│            restart,\n 1443│        } => match action {\n*1444│            Some(UpdateCmd::Apply { finish }) => cmd_update_apply(finish),\n*1445│            Some(UpdateCmd::Fetch {\n 1446│                channel,\n 1447│                tag,\n 1448│                apply,\n     │...\n 1450│            // [impl->REQ-UPDATE-ADAPTERS-VERB] the alias form; one engine.\n*1451│            Some(UpdateCmd::Adapters { names }) => {\n 1452│                cmd_adapter_update(&spt_store::perch::adapters_dir(), names.as_deref())\n 1453│            }\n 1454│            // [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n*1455│            // [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\n*1456│            None => cmd_update_composite(core_only, restart),\n 1457│        },\n 1458│        // [impl->REQ-INSTALL-BOOTSTRAP-VERB]\n 1459│        Cmd::Install { dir, no_path } => cmd_install(dir, no_path),\n     │...\n 3878│/// - A managed service is already active ⇒ warn loudly (running inline fights\n*3879│///   it for the socket — the kitsubito 2026-06-08 restart-loop).\n 3880│// [impl->REQ-DAEMON-3]\n 3881│// [impl->REQ-DAEMON-7]\n 3882│// [impl->REQ-CLI-2]\n### rc.rs\n 1257│                // went down and never came back — rc did NOT resurrect it. Name that\n*1258│                // plainly so the operator restarts the daemon rather than guessing.\n 1259│                let _ = writeln!(\n 1260│                    stdout,\n 1261│                    \"\\r\\n[session '{endpoint_id}' lost — the spt daemon is down and \\\n     │...\n 1290│                 wedged. Detached. Try `spt endpoint list` for its state, or \\\n*1291│                 `spt endpoint run` to restart it]\"\n 1292│            );\n 1293│            Ok(())\n 1294│        }\n     │...\n 1327│            \"the daemon didn't answer in time while attaching to '{endpoint_id}' — \\\n*1328│             it may be busy or mid-restart. Try `spt rc {endpoint_id}` again; if it \\\n 1329│             keeps happening, `spt endpoint list` shows the endpoint's state\"\n 1330│        )\n 1331│    } else {\n     │...\n 1337│/// stream, and the op counter that minted it. One per (re)connect — the B-3\n*1338│/// reconnect loop re-mints the WHOLE bundle (a broker restart drops the conn,\n 1339│/// not just the stream; a fresh OpMinter keeps ADR-0034 `rc` op tracing per\n 1340│/// viewport). [impl->REQ-RC-RECONNECT]\n 1341│struct Established {\n     │...\n 1453│    // A-4b (REQ-OPID-TRACING-RETRY): the attach stream-open is a tracing-only op.\n*1454│    // On the broker-restart no-longer-held reply, re-mint a FRESH `rc` op and open\n 1455│    // ONCE more. The first attempt uses the conn already established above (happy\n*1456│    // path byte-identical); the one retry RE-DIALS inside `run` — a broker restart\n 1457│    // drops the CONN too, not just the stream, so opening against the dead conn\n 1458│    // would surface a different error instead of self-healing. A 2nd no-longer-held\n 1459│    // surfaces the helper's F-1 public string via the map_err below (never op lingo).\n     │...\n 1603│                // may still be coming back. DaemonDown included: rc waits for the\n*1604│                // operator to restart it, it does NOT auto-start one.\n 1605│                Err(_) => std::thread::sleep(RECONNECT_PAUSE),\n 1606│            }\n 1607│        };\n     │...\n 1634│/// Generous: a daemon bounce (`spt daemon stop` + the next auto-start) and a\n*1635│/// node's broker restart both land well inside it.\n 1636│const RECONNECT_WINDOW: Duration = Duration::from_secs(30);\n 1637│/// The pause between reconnect attempts (each attempt already carries its own\n 1638│/// bounded setup deadlines — this just keeps the loop from hammering).\n     │...\n 1847│    }\n*1848│    // Dedup cursor: a post-restart re-serve may re-transmit `Output` records;\n 1849│    // drop any `seq` already rendered (the operator render cursor, matching the\n 1850│    // local `OutputLog` replay discipline).\n 1851│    let mut cursor = 0u64;\n     │...\n 2523│        assert!(down.contains(\"won't auto-start\"), \"states rc won't resurrect it\");\n*2524│        assert!(down.contains(\"spt daemon start\"), \"points at the restart\");\n 2525│        let generic = render(\"the connection was severed\", false);\n 2526│        assert!(!generic.contains(\"won't auto-start\"), \"generic omits the daemon-down line\");\n 2527│        assert!(generic.contains(\"didn't succeed\"), \"keeps the didn't-reconnect copy\");\n### wansend.rs\n 359│    // pre-minted epoch, then a genuinely FRESH cli epoch on the one retry the\n*360│    // broker-restart no-longer-held reply triggers. CLI-originated rest op —\n 361│    // cli-ops.json is the counter (ADR-0034 `cli` tag).\n 362│    let mut minted_first = false;\n 363│    let mint = move || -> std::io::Result<MintedOp> {\n    │...\n 428│    // A-4b (REQ-OPID-TRACING-RETRY): wrap the rest round-trip in the tracing-only\n*429│    // one-shot retry. The re-dial rides INSIDE `run` — a broker restart drops the\n 430│    // CONN too, not just the stream, so the retry must open a fresh conn. `op_id`\n 431│    // (tracing correlation) is intentionally stable across the retry; only the\n 432│    // journal open-op is re-minted. A 2nd no-longer-held surfaces the helper's F-1\n\n### api/\n#### mod.rs\n 76│        /// Bind from this session id when the ephemeral seed is gone (a session\n*77│        /// going live late, or after a daemon restart). With no live seed and\n 78│        /// no session id, listen refuses (NO_SEED).\n 79│        #[arg(long)]\n 80│        session_id: Option<String>,\n#### startup.rs\n  80│             (a stale pre-0.9.0 broker cannot read the new seed format) — run \\\n *81│             `spt daemon stop`, then retry (the broker restarts on the next \\\n  82│             `spt api` call)\"\n  83│        )\n  84│    } else {\n    │...\n 207│/// Bind WITHOUT a seed, from an explicit session id (F-034 leg c): a session\n*208│/// that goes live LATE (hours after SessionStart, or after a daemon restart)\n 209│/// finds its ephemeral seed gone — nothing re-fires it until the NEXT\n 210│/// SessionStart — so `listen --session-id <sid>` binds directly.\n 211│///\n    │...\n 588│        Ok(t) => t,\n*589│        // Late go-live / post-daemon-restart (F-034 leg c): the ephemeral seed\n 590│        // is gone and nothing re-fires it until the NEXT SessionStart, so an\n 591│        // explicit --session-id binds directly. NO_SEED only — any OTHER\n 592│        // refusal keeps its own diagnostic (the sid fallback must never paper\n    │...\n 630│    // shellwake CLI-flips-status / brain-reconciles pattern). `status` is\n*631│    // daemon-authoritative (CONTEXT:194). A new `listen` against a not-yet-restarted\n 632│    // OLD brain leaves the perch online-but-unhosted; that self-heals at the brain's\n 633│    // next BOOT reconcile (livehost reconciles at boot, not only on tick) — the\n 634│    // agent + the message relay below are live throughout regardless.\n\n## tests/\n### brain_split.rs\n  95│/// D3-1 (ADR-0018 Q2) — the seed-control lock + liveness is the broker's, so it\n *96│/// is the **always-up anchor**: a brain restart must never perturb it. The\n  97│/// single-daemon-per-`SPT_HOME` ping (`is_running`, what `ensure_running` /\n  98│/// `daemon stop` target) lives in the broker's foreground loop\n  99│/// (`daemon.rs::Daemon::run`), NOT the brain — so killing the brain and watching\n    │...\n 128│    // Kill the brain; while the supervisor respawns it, the seed anchor (in the\n*129│    // never-restarting broker) must stay continuously up. Sample across the whole\n 130│    // respawn gap — a single not-running read is a failure (the anchor flapped).\n 131│    kill_pid(pid1);\n 132│    let deadline = Instant::now() + Duration::from_secs(30);\n### brain_survive.rs\n   2│//! swap**: a hosted PTY child + a live QUIC connection survive a brain-**PROCESS**\n  *3│//! restart that lands on a **swapped on-disk binary**, with the broker holding\n   4│//! both endpoints untouched throughout. This is the `int` E2E that re-points the\n   5│//! regression-masked REQ-DAEMON-2 / REQ-UPD-3 evidence off the in-process\n   6│//! `brain_swap.rs` shape onto a real process boundary.\n    │...\n  18│//!   a live QUIC conn). The broker is modeled **in-process** — faithful, not a\n *19│//!   weakening: the broker is the never-restarting anchor *by design*, so an\n  20│//!   in-process [`Broker`] runs the **same Broker code holding the same\n  21│//!   resources** the real broker would across a brain swap. The claim under test\n  22│//!   is the brain process + the swapped binary + broker-held endpoint survival —\n    │...\n  68│use spt_daemon::brain::Brain;\n *69│use spt_daemon::brainproc::{supervise_brain, BrainRestart, NoTrialEnv, StartReason};\n  70│use spt_daemon::codec::write_frame;\n  71│use spt_daemon::endpoint::broker_socket_name;\n  72│use spt_daemon::frame::{Envelope, Role};\n    │...\n 189│#[test]\n*190│fn pty_and_quic_survive_brain_process_restart_onto_swapped_binary() {\n 191│    // The in-process broker + brain children both resolve `broker_socket_name()`\n 192│    // from SPT_HOME, so the supervised child connects to OUR broker. (The\n 193│    // established daemon-test pattern — daemon_e2e.rs et al. set it the same way.)\n    │...\n 254│    //    pre-D6 respawn loop (no readiness gate); the swap is driven by the\n*255│    //    planned-restart signal, exactly as a real `apply` drives it (D3-3). ──\n 256│    let ready_path = home.path().join(\"brain.ready\");\n 257│    let stop = Arc::new(AtomicBool::new(false));\n*258│    let restart = Arc::new(BrainRestart::new());\n 259│    let selected = Arc::new(Mutex::new(fixture_a.clone()));\n 260│    let home_path = home.path().to_path_buf();\n 261│\n    │...\n 263│        let stop = Arc::clone(&stop);\n*264│        let restart = Arc::clone(&restart);\n 265│        let selected = Arc::clone(&selected);\n 266│        thread::spawn(move || {\n 267│            supervise_brain(\n 268│                &stop,\n*269│                &restart,\n 270│                Duration::from_millis(50),\n 271│                &NoTrialEnv,\n 272│                Duration::from_secs(30),\n    │...\n 308│\n*309│    // ── SWAP: flip the selected binary to B, then trigger a planned restart\n 310│    //    (what `apply` does: swap on disk, signal the brain to cycle). ──\n 311│    *selected.lock().unwrap() = fixture_b.clone();\n*312│    restart.request();\n 313│\n 314│    // ── Generation 1: the supervisor respawned the brain PROCESS onto the\n 315│    //    SWAPPED binary. New pid, exe_hash == B != A (NEW BYTES RAN). ──\n### daemon_refresh_e2e.rs\n 197│\n*198│    // The broker process must be the SAME OS process (never restarted).\n 199│    let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);\n 200│\n 201│    // The hosted endpoint survives: harness alive + perch back/still ONLINE.\n### endpoint_autostart_e2e.rs\n  *1│//! F-038 W5 — REQ-ENDPOINT-AUTOSTART (restart-brings-it-up acceptance).\n   2│//!\n   3│//! The gap this locks down (autostart.rs header, deployah field-confirm\n   4│//! 2026-07-10): a gateway-class endpoint is infra — the phone treats `mobile-gw`\n    │...\n   8│//! runs on a boot background thread (`replay_startup_endpoints`, after broker\n  *9│//! bind) so a restart brings the endpoint back up on its own — a FRESH session\n  10│//! (shape (a): a startup default, not a session restore).\n  11│//!\n  12│//! This gate proves the FIX end to end by driving the REAL `spt` binary across a\n *13│//! full daemon RESTART, all inside a fully SPT_HOME-isolated home (its own\n  14│//! daemon, never the operator's live one — the idle_edge_drain_e2e isolation\n  15│//! precedent):\n  16│//!   1. a REAL daemon (A) hosts a REAL spt-hosted `cc` endpoint;\n    │...\n  59│/// anywhere in the body still tears the isolated daemon/harness/psyche tree down\n *60│/// (the restart flow has many more panic points than a single-daemon gate).\n  61│struct Reaper {\n  62│    pids: Mutex<Vec<u32>>,\n  63│}\n    │...\n 149│#[test]\n*150│fn saved_endpoint_replays_on_daemon_restart() {\n 151│    let reaper = Reaper::new();\n 152│\n 153│    // ── (1) Isolated, NET-LESS SPT_HOME. ──\n    │...\n 259│    // the fresh replay must supersede), then reap the run-A harness + psyche so\n*260│    // the restart brings a genuinely NEW session up, not a survivor.\n 261│    let rec_a = spt_store::info::read_info(&self_perch);\n 262│    let session_a = rec_a.as_ref().map(|r| r.session_id.clone());\n 263│    if let Some(pid) = spt_store::info::read_pid(&self_perch) {\n    │...\n 287│\n*288│    // ── (6) Daemon B — the RESTART. NO `endpoint run`. Its boot replay thread\n 289│    //    (after broker bind) must bring `gwauto` back up on its own. ──\n 290│    let daemon_b_log = home.path().join(\"daemon-b.stderr.log\");\n 291│    let log_b = std::fs::File::create(&daemon_b_log).expect(\"create daemon B log\");\n    │...\n 322│        // Done once the loud token is present AND the perch carries a fresh\n*323│        // (post-restart) session id distinct from run A's.\n 324│        let fresh = matches!((&session_a, &session_b), (Some(a), Some(b)) if !b.is_empty() && a != b);\n 325│        if autostart_line.is_some() && fresh {\n 326│            break;\n    │...\n 342│    eprintln!(\n*343│        \"=== F-038 W5 AUTOSTART-ON-RESTART GATE: online_a={online_a} run_ok={} \\\n 344│         saved_token={} session_a={session_a:?} session_b={session_b:?} \\\n 345│         autostart_line={autostart_line:?} ===\\n=== endpoint run stderr ===\\n{run_stderr}\\n\\\n 346│         === daemon.json after --save ===\\n{cfg_after_save}\\n\\\n    │...\n 373│        online_a,\n*374│        \"PRECONDITION: the cc endpoint must bind ONLINE under daemon A before restart\"\n 375│    );\n 376│    // (a) The save fired loud AND landed in daemon.json.\n 377│    assert!(\n    │...\n 387│        brain_b.is_some(),\n*388│        \"PRECONDITION: daemon B (the restart) must come up with a fresh brain.\\n\\\n 389│         === daemon B stderr ===\\n{daemon_b_stderr}\"\n 390│    );\n*391│    // (b) THE KEYSTONE: the restart's boot replay brought the saved endpoint back\n 392│    //     up on its own — the loud ENDPOINT_AUTOSTART:<id> token is present in\n 393│    //     daemon B's stderr, emitted only on a successful launch_harness_brokered_in.\n 394│    let line = match autostart_line {\n    │...\n 406│    // (c) A replay is a FRESH session (shape (a): a startup default, not a\n*407│    //     restore) — the post-restart perch must carry a session id distinct from\n 408│    //     run A's.\n 409│    if let (Some(a), Some(b)) = (&session_a, &session_b) {\n 410│        assert!(\n### listen_seed_retry_e2e.rs\n  10│//! 2. **Session-id fallback** (REQ-LISTEN-SESSION-ID-FALLBACK): a session whose\n *11│//!    ephemeral seed is GONE (late go-live / post-daemon-restart) binds via\n  12│//!    `api listen <id> --session-id <sid>` — the loud SID_BIND marker path.\n  13│//!\n  14│//! Both drive the real binary with an explicit `--adapter mock` (bin-only crate:\n    │...\n 166│// [int->REQ-LISTEN-SESSION-ID-FALLBACK] F-034 leg c: a session with NO live seed\n*167│// (its ephemeral SessionStart seed expired / a daemon restart dropped it) binds via\n 168│// `api listen <id> --session-id <sid>`. The seed path yields NO_SEED, the fallback\n 169│// binds from the sid (loud SID_BIND marker), and the perch carries the sid. This is\n*170│// the post-daemon-restart shape — the seed is simply absent.\n 171│#[test]\n 172│fn no_seed_session_binds_via_session_id_fallback() {\n 173│    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());\n### live_adapt_translation_swap_e2e.rs\n   6│//! `Brain::adapter_apply` → stop the resident translation child (frees the OS lock)\n  *7│//! → CRC-swap the install dir → restart the translation from the new manifest path\n   8│//! — and the previously-LOCKED install-dir binary is overwritten with the NEW bytes\n  *9│//! WHILE the endpoint/session keeps running untouched (brain-parity, no restart).\n  10│//!\n  11│//! Real `spt daemon run` broker + supervised brain + a REAL dummy-harness\n  12│//! `[session.self]` session + a REAL translation binary (`translate_proof_fixture`,\n    │...\n 108│#[test]\n*109│fn adapter_apply_swaps_locked_translation_binary_without_restarting_endpoint() {\n 110│    // Serialize against the sibling tests — shared process-global SPT_HOME.\n 111│    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());\n 112│    let now_ms = || {\n    │...\n 240│    //    This proves the binary IS locked — the exact condition the daemon-coordinated\n*241│    //    apply must work around (stop child → swap → restart). On Unix this won't lock,\n 242│    //    so we skip the pre-assert and just proceed to the apply. ──\n 243│    #[cfg(windows)]\n 244│    let direct_overwrite_locked = {\n    │...\n 286│    //    broker stops the resident translation child (frees the lock), CRC-swaps the\n*287│    //    install dir from staging, restarts the translation from the new path. ──\n 288│    let mut brain = Brain::cold_start(&broker_socket_name(), now_ms())\n 289│        .expect(\"connect a Brain to the live broker for adapter_apply\");\n 290│    let apply_result = brain.adapter_apply(&AdapterApplyReq {\n    │...\n 299│    //        LANDED on the previously-locked binary);\n*300│    //    (b) the session/endpoint is STILL alive (no restart — brain-parity): the\n 301│    //        harness pid is alive AND the perch is still ONLINE. ──\n 302│    let hash_after = file_hash(&xlate_install);\n 303│    let swap_landed = hash_after.is_some()\n    │...\n 377│        \"REQ-ADAPTER-LIVE-UPDATE: Brain::adapter_apply must return Ok (the broker stopped \\\n*378│         the resident translation child, CRC-swapped the install dir, and restarted the \\\n 379│         translation) — got {:?}.\\n=== brain stderr ===\\n{brain_stderr}\",\n 380│        apply_result.err().map(|e| e.to_string())\n 381│    );\n    │...\n 392│\n*393│    // Brain-parity: the endpoint NEVER restarted across the live update.\n 394│    assert!(\n 395│        harness_alive_after,\n 396│        \"REQ-ADAPTER-LIVE-UPDATE (brain-parity): the harness session must STILL be ALIVE \\\n 397│         after the live update — only the translation child cycles, the endpoint never \\\n*398│         restarts. harness_pid={harness_pid:?} was dead post-apply.\\n\\\n 399│         === brain stderr ===\\n{brain_stderr}\"\n 400│    );\n 401│    assert!(\n    │...\n 792│    );\n*793│    // Brain-parity: the composite endpoint never restarted across the live update.\n 794│    assert!(\n 795│        harness_alive_after,\n 796│        \"REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP (brain-parity): the cc:prof harness must STILL be \\\n### projindex_writer_e2e.rs\n   7│//! daemon start (index published from nothing, batched counters on the status\n  *8│//! surface) → daemon restart (warm start: unchanged generation performs NO\n   9│//! scan, the persisted snapshot is byte-untouched) → the five invalidation\n  10│//! event classes fired through their PRODUCTION funnels (the store's own\n  11│//! nudges for context/rename/fork/purge via the real CLI verbs / ContextStore\n    │...\n 209│\n*210│    // ── (3) WARM START: restart the daemon over the unchanged store. ──\n 211│    let _ = {\n 212│        let mut cmd = Command::new(&spt_bin);\n 213│        cmd.no_window()\n    │...\n 273│        bytes_before,\n*274│        \"the unchanged snapshot is byte-untouched across the restart\"\n 275│    );\n 276│\n 277│    // ── (4) EVENT INVALIDATIONS against the LIVE daemon. ──\n### resume_no_control_steal_e2e.rs\n  97│\n *98│use spt_daemon::brainproc::{ready_generation_at, supervise_brain, BrainRestart, StartReason, TrialEnv};\n  99│use spt_daemon::codec::{read_frame, write_frame};\n 100│use spt_daemon::endpoint::broker_socket_name;\n 101│use spt_daemon::frame::{Envelope, Role};\n    │...\n 399│        thread::spawn(move || {\n*400│            let restart = BrainRestart::new();\n 401│            supervise_brain(\n 402│                &stop_sup,\n*403│                &restart,\n 404│                Duration::from_millis(50),\n 405│                env_sup.as_ref(),\n 406│                Duration::from_secs(60),\n### trial_drain_drive_e2e.rs\n  23│//! candidate DRIVES the live older broker's already-shipped reap and un-strands its\n *24│//! OWN trial with no coordinated broker restart.\n  25│//!\n  26│//! ## Why the wedge is established AFTER the candidate boots (the resume paradox)\n  27│//!\n    │...\n 108│use spt_daemon::brain::Brain;\n*109│use spt_daemon::brainproc::{ready_generation_at, supervise_brain, BrainRestart, StartReason, TrialEnv};\n 110│use spt_daemon::codec::{read_frame, write_frame};\n 111│use spt_daemon::endpoint::broker_socket_name;\n 112│use spt_daemon::frame::{Envelope, Role};\n    │...\n 373│        thread::spawn(move || {\n*374│            let restart = BrainRestart::new();\n 375│            supervise_brain(\n 376│                &stop_sup,\n*377│                &restart,\n 378│                Duration::from_millis(50),\n 379│                env_sup.as_ref(),\n 380│                Duration::from_secs(60), // generous window — covers a slow-CI boot (eaten before the\n### twohost_cli.rs\n  31│//!    honest both-sockets death: seed + broker die with the process) → A's\n *32│//!    child paints the reconnect banner → B's daemon restarts and the ticker\n  33│//!    respawns → the viewport reheals on the production loop.\n  34│//!\n  35│//! ## Rendezvous (differs from the ladder — deliberately)\n    │...\n 695│    daemon.stop();\n*696│    println!(\"GATED role B: daemon DOWN (honest stop) — restarting\");\n 697│    let daemon2 = DaemonGuard::start(home.path(), &spt, \"b2\");\n 698│    // Re-anchor the ticker perch on generation 2's pid BEFORE anything\n 699│    // re-advertises: the old anchor died with generation 1, and a dead-pid row\n\n# docs-site/src/\n\n## cli/\n### reference.md\n  421│  stop     Stop the daemon (service-aware: a managed service is stopped via its manager so it does\n *422│           not auto-restart-fight; else a graceful IPC stop). Refuses with a warning if it hosts\n  423│           live sessions (they would be killed) — pass --force to stop anyway\n  424│  status   Node status: daemon state, member subnets, local endpoints (the bare spt daemon view)\n *425│  refresh  Restart the daemon's coordinator process in place — no binary change, no stop/start.\n  426│           Hosted terminals and the network layer keep running untouched; only the coordinator\n  427│           cycles. The recovery verb for a stuck coordinator (e.g. endpoint bringup wedged) that\n  428│           previously needed a full daemon stop/start killing every hosted session\n     │...\n  475│Stop the daemon (service-aware: a managed service is stopped via its manager so it does not\n *476│auto-restart-fight; else a graceful IPC stop). Refuses with a warning if it hosts live sessions\n  477│(they would be killed) — pass --force to stop anyway\n  478│\n  479│Usage: spt daemon stop [OPTIONS]\n     │...\n  506│```text\n *507│Restart the daemon's coordinator process in place — no binary change, no stop/start. Hosted\n  508│terminals and the network layer keep running untouched; only the coordinator cycles. The recovery\n  509│verb for a stuck coordinator (e.g. endpoint bringup wedged) that previously needed a full `daemon\n  510│stop/start` killing every hosted session\n     │...\n 1011│      --save\n*1012│          Also persist as the startup default (survives daemon restarts)\n 1013│\n 1014│  -h, --help\n 1015│          Print help (see a summary with '-h')\n     │...\n 1022│\n*1023│Advertising + connecting restart within one pump cadence.\n 1024│\n 1025│Usage: spt subnet attach [OPTIONS] <NAME>\n 1026│\n     │...\n 1037│      --save\n*1038│          Also persist as the startup default (survives daemon restarts)\n 1039│\n 1040│  -h, --help\n 1041│          Print help (see a summary with '-h')\n     │...\n 1107│\n*1108│      --restart\n*1109│          The full-cycle form: fetch, update adapters, then finish by restarting the daemon onto the\n*1110│          new version (update apply --finish) as the final step — so the whole node, coordinator\n*1111│          and live agents, runs the new version when it returns. The restart bounces hosted sessions\n 1112│          (they come back automatically)\n 1113│\n 1114│  -h, --help\n     │...\n 1125│Options:\n*1126│      --finish  Finish onto the new version in one step: install it, then restart the daemon so both\n 1127│                the coordinator and every live agent run the new version. Hosted sessions come back\n*1128│                automatically — no manual restart. Without this flag, install alone leaves the\n*1129│                running daemon on the previous version until you restart it yourself\n 1130│      --json    Emit machine-readable JSON instead of the human view. Honored by the read/status\n 1131│                commands (list, whoami, status, description, role, the *-list queries, how-to);\n 1132│                action commands ignore it\n     │...\n 1298│      --session-id <SESSION_ID>  Bind from this session id when the ephemeral seed is gone (a\n*1299│                                 session going live late, or after a daemon restart). With no live\n 1300│                                 seed and no session id, listen refuses (NO_SEED)\n 1301│  -h, --help                     Print help\n 1302│```\n\n## concepts/\n### overview.md\n 62│  processes, listening sockets. It almost never updates.\n*63│- the **brain** holds all logic and restarts freely. An update swaps the\n 64│  brain while the broker keeps every session's process and byte stream\n 65│  intact — running agents don't notice.\n 66│\n\n## harness-contract/\n### api.md\n  98│and survives until exactly one of: a successful `listen` bind consumes it, a\n *99│newer `seed` for the same pid overwrites it, or the daemon process restarts\n 100│(which drops the whole map). Nothing re-fires it until the harness's **next**\n 101│SessionStart. So an adapter must not rely on the seed for a session that goes\n*102│live late (hours after SessionStart) or after a daemon restart — that is what\n 103│`listen --session-id` (below) is for.\n 104│\n 105│### `api listen <id> [--once] [--parent-pid <pid>] [--subnet <name>] [--session-id <sid>]`\n    │...\n 123│**`--session-id <sid>` — binding when the seed is gone.** A session that goes\n*124│live late, or after a daemon restart, finds no live seed; with `--session-id`\n 125│the listener binds directly from the given harness session id (loud\n 126│`SID_BIND:<id>` marker). The fallback fires **only** on `NO_SEED` — every\n 127│other refusal keeps its own diagnostic — and carries the same identity/auth\n\n## instances/\n### overview.md\n 32│Infrastructure endpoints (a gateway the phone treats as always-there) should\n*33│not need hands-on bringup after a box reboot or daemon restart. `spt endpoint\n 34│run … --save` persists the run — endpoint id, adapter option, and working\n 35│directory — as a **startup default** in `daemon.json`; the daemon **replays**\n 36│every saved default when it starts, as a fresh session with the adapter\n   │...\n 45│This is a **startup default**, not a session restore — it replays what you\n*46│saved, never \"whatever was up before the restart\" — and it is symmetric with\n 47│`spt subnet attach --save` (the serve-state startup default).\n 48│\n 49│## Commands"},"isError":false,"timestamp":1784247426566}}
{"type":"message","id":"a50f2299","parentId":"be206e1b","timestamp":"2026-07-17T00:17:06.705Z","message":{"role":"toolResult","toolCallId":"call_aVMZ0BXJMgsk5a9FC6zmeDN1|fc_099a7567dadef391016a5974826270819bbd4eea7b245cdf81","toolName":"read","content":[{"type":"text","text":"fn cmd_shutdown(id: Option<String>) -> i32 {\n…\n    if id.contains('@') || id.contains(':') {\n…\n    }\n    // Leg 1: soft-stop — the listener marker goes first so nothing new is\n    // accepted while the edge runs.\n    let _ = std::fs::remove_file(perch::resolve_ready_file(&id, ParentHint::Infer));\n    // Leg 2: the suspend edge (echo gate + shell cascade live in the host).\n    // Leg-2 stays LOCAL_ONLY (REQ-REST-VERB-ROUTING): shutdown is an endpoint's\n    // OWN self-suspend — a bare-id miss must NOT route the verb to some other\n    // node's instance. Pass `false` so the A-3 remote fallback never engages.\n    cmd_rest(&id, spt_daemon::RestEvent::Suspend, \"SHUTDOWN\", false)\n}\n\n/// Run the per-machine daemon (`spt daemon run`, M8 decision 5). FOREGROUND +\n/// unelevated by contract (REQ-DAEMON-7): this process IS the daemon, blocking\n/// the broker + seed-control channels until signalled. Never auto-detaches —\n/// the background/de-elevated path lives in `start`.\n///\n/// Two guards before binding:\n/// - Windows + elevated ⇒ REFUSE (exit 2). An elevated `run` would otherwise\n///   respawn detached/de-elevated (KH 5.7) and vanish into the background.\n/// - A managed service is already active ⇒ warn loudly (running inline fights\n///   it for the socket — the kitsubito 2026-06-08 restart-loop).\n// [impl->REQ-DAEMON-3]\n// [impl->REQ-DAEMON-7]\n// [impl->REQ-CLI-2]\nfn cmd_daemon_run(detached: bool) -> i32 {\n    // Broker self-detach (REQ-HAZARD-WMI-DAEMON-WINDOW): when the auto-start ladder\n    // launched us with `--detached`, drop our console FIRST so the conhost the\n    // WMI/schtasks `start /b` rung allocated exits — before any output or bind. A\n    // human-typed foreground `spt daemon run` omits the flag and keeps its console.\n    #[cfg(windows)]\n    if detached {\n        spt_daemon::daemon::detach_console();\n    }\n    // Persist the broker's stderr to the rotating SPT_HOME/logs sink\n    // (REQ-DAEMON-STDERR-PERSIST) BEFORE the null-handles guard below: redirecting to\n    // a FILE first means the pipe-defense then sees a safe (non-pipe) file and leaves\n    // it alone — so a detached broker's diagnostics land on disk instead of /dev/null\n    // (the incident-night RCA-blind gap). The broker is the stable half (no brain\n    // generation) → stamp 0; the role tag distinguishes it. Best-effort.\n    // [impl->REQ-DAEMON-STDERR-PERSIST]\n    let _ = spt_daemon::stderrlog::install(\"broker\", 0);\n    // Defense-in-depth (REQ-HAZARD-DETACHED-DAEMON-STDIO): null the std handles when\n    // detached-in-fact (no console OR a piped stderr) EVEN IF `--detached` was not\n    // passed — the stale-bare-at-logon-task / bare-deelevated-respawn / any-unknown\n    // rung case. Runs AFTER detach_console (idempotent) and BEFORE any bind/output, so\n    // a filling inherited pipe can never block the daemon. A foreground human keeps\n    // their console (both checks false).\n    #[cfg(windows)]\n    spt_daemon::daemon::null_std_handles_if_detached_in_fact();\n    #[cfg(not(windows))]\n    let _ = detached; // unix detaches via setsid in spawn_detached; flag unused.\n    #[cfg(windows)]\n    if spt_daemon::service::run_refused_when_vanishing(\n        true,\n        spt_daemon::deelevate::has_deelevation_target(),\n    ) {\n        // Only the vanishing case: elevated WITH an unelevated desktop shell to\n        // drop to (run would respawn detached/de-elevated). A headless or\n        // uniformly-elevated session has no target and serves foreground below.\n        eprintln!(\n            \"DAEMON_RUN_ELEVATED: `daemon run` is foreground; from an elevated shell it would \\\n             respawn in the background — use `spt daemon start` for a background daemon, or run \\\n             from an unelevated shell\"\n        );\n        return 2;\n    }\n    if let Some(label) = spt_daemon::active_service_conflict() {\n        eprintln!(\n            \"DAEMON_RUN_CONFLICT: a managed {label} service is active; running inline will \\\n             fight it for the socket — `spt daemon stop` first, or use `spt daemon start`\"\n        );\n    }\n    match spt_daemon::Daemon::run() {\n        Ok(()) => 0,\n        Err(e) => {\n            eprintln!(\"DAEMON_EXIT: {e}\");\n            1\n        }\n    }\n}\n\n/// Run the brain child (`spt daemon brain`, hidden — ADR-0018 D1). The broker\n/// process (`daemon run`) spawns + supervises this; it is not a user-facing\n/// entry. FOREGROUND: this process IS the brain, blocking until the broker\n/// connection drops (then it exits for a supervised respawn). `generation` +\n/// `start_reason` are the broker's spawn-time stamp (ADR-0018 V2), defaulted so\n/// an older broker's bare argv still parses (KH-2.3).\n// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\nfn cmd_daemon_brain(generation: u64, start_reason: &str) -> i32 {\n    match spt_daemon::Daemon::run_brain(generation, start_reason) {\n        Ok(()) => 0,\n        Err(e) => {\n            eprintln!(\"BRAIN_EXIT: {e}\");\n            1\n        }\n    }\n}\n\n/// `spt daemon start` (REQ-DAEMON-6) — ensure the daemon is up in the\n/// background, idempotent + service-aware: a registered OS service is driven\n/// via its manager (never a competing manual daemon), else a detached spawn.\n// [impl->REQ-DAEMON-6]\nfn cmd_daemon_start() -> i32 {\n    match spt_daemon::start_daemon() {\n        Ok(spt_daemon::StartOutcome::AlreadyRunning) => {\n            eprintln!(\"DAEMON_ALREADY_RUNNING\");\n            0\n        }\n        Ok(spt_daemon::StartOutcome::ServiceStarted(label)) => {\n            eprintln!(\"DAEMON_STARTED (via {label})\");\n            0\n        }\n        Ok(spt_daemon::StartOutcome::Spawned) => {\n            eprintln!(\"DAEMON_STARTED\");\n            0\n        }\n        Err(e) => {\n            eprintln!(\"DAEMON_START_FAIL: {e}\");\n            1\n        }\n    }\n}\n\n/// `spt daemon stop` — graceful, service-aware shutdown (REQ-DAEMON-6, M8\n/// decision 5 / REQ-CLI-2): a managed service is stopped through its manager\n/// (so it does not auto-restart-fight); a manual daemon acks the seed-control\n/// stop then exits. Idempotent UX: not-running is already-stopped (exit 0).\n/// The live-session stop guard (REQ-DAEMON-STOP-LIVE-SESSION-WARN): pure — a daemon\n/// hosting live sessions REFUSES to stop (naming them) unless `--force`, so a stop\n/// never silently kills hosted endpoints out from under an operator. Empty set (no\n/// hosted sessions) or `--force` ⇒ proceed. `Err` carries the operator warning.\n// [impl->REQ-DAEMON-STOP-LIVE-SESSION-WARN]\nfn stop_live_session_guard(live_sessions: &[String], force: bool) -> Result<(), String> {\n    if live_sessions.is_empty() || force {\n        return Ok(());\n    }\n    Err(format!(\n        \"the daemon is hosting {} live session(s) that stopping would kill: {}. \\\n         Re-run with `spt daemon stop --force` to stop anyway — they come back on the \\\n         next `spt daemon start` — or leave the daemon running.\",\n        live_sessions.len(),\n        live_sessions.join(\", \")\n    ))\n}\n\n/// The endpoint ids the running daemon currently hosts a broker PTY session for —\n/// the stop-guard's \"would be killed\" set. Best-effort: a down/unreachable daemon\n/// yields none (the guard is then a no-op and the normal stop path reports\n/// already-stopped). // [impl->REQ-DAEMON-STOP-LIVE-SESSION-WARN]\nfn live_hosted_session_ids() -> Vec<String> {\n    if !spt_daemon::is_running() {\n        return Vec::new();\n    }\n    match spt_daemon::brain::Brain::cold_start(&spt_daemon::broker_socket_name(), now_ms()) {\n        Ok(mut b) => b\n            .sessions()\n            .map(|r| {\n                r.sessions\n                    .into_iter()\n                    .map(|s| s.endpoint)\n                    .filter(|e| !e.is_empty())\n                    .collect()\n            })\n            .unwrap_or_default(),\n        Err(_) => Vec::new(),\n    }\n}\n\n// [impl->REQ-DAEMON-6]\n// [impl->REQ-CLI-2]\n// [impl->REQ-DAEMON-STOP-LIVE-SESSION-WARN]\nfn cmd_daemon_stop(force: bool) -> i32 {\n    // Guard: refuse + name the sessions if stopping would kill live hosted endpoints,\n    // unless --force. Runs BEFORE the stop so nothing is torn down on a refusal.\n    let live = live_hosted_session_ids();\n    if let Err(msg) = stop_live_session_guard(&live, force) {\n        eprintln!(\"DAEMON_STOP_REFUSED: {msg}\");\n        return 3;\n    }\n    match spt_daemon::stop_daemon() {\n        Ok(spt_daemon::StopOutcome::NotRunning) => {\n            eprintln!(\"DAEMON_NOT_RUNNING (already stopped)\");\n            0\n        }\n        Ok(spt_daemon::StopOutcome::ServiceStopped(label)) => {\n            eprintln!(\"DAEMON_STOPPED (via {label})\");\n            0\n        }\n        Ok(spt_daemon::StopOutcome::IpcStopped) => {\n            eprintln!(\"DAEMON_STOPPED\");\n            0\n        }\n        Err(e) => {\n            eprintln!(\"DAEMON_STOP_FAIL: {e}\");\n            1\n        }\n    }\n}\n\n/// `spt daemon refresh` — cycle the daemon's brain in place with NO binary swap\n/// (REQ-DAEMON-REFRESH): the apply_staged brain-cycle path minus the swap, so the\n/// broker and every hosted PTY survive by construction. Requires a running\n/// daemon (nothing to refresh otherwise — `daemon start` is the cold path). A\n/// broker without a supervisor wired accepts but cannot act; that is loud, not\n/// silent — the operator asked for a cycle that did not happen.\n// [impl->REQ-DAEMON-REFRESH]\nfn cmd_daemon_refresh() -> i32 {\n    if !spt_daemon::is_running() {\n        eprintln!(\"DAEMON_NOT_RUNNING: nothing to refresh — `spt daemon start` brings it up fresh\");\n        return 1;\n    }\n    match spt_daemon::refresh_brain(&spt_daemon::broker_socket_name()) {\n        Ok(true) => {\n            println!(\n                \"Daemon coordinator restarted in place — hosted sessions and connections were \\\n                 not touched. Run `spt daemon status` to confirm.\"\n            );\n            0\n        }\n        Ok(false) => {\n            eprintln!(\n                \"DAEMON_REFRESH_NOT_HONORED: the daemon accepted the request but has no \\\n                 supervisor to cycle its coordinator (an older or unmanaged daemon) — use \\\n                 `spt daemon stop` then `spt daemon start` instead (hosted sessions come back \\\n                 automatically).\"\n            );\n            1\n        }\n        Err(e) => {\n            eprintln!(\"DAEMON_REFRESH_FAIL:{e}\");\n            1\n        }\n    }\n}\n\n/// The connection + peer-pump status lines (REQ-DAEMON-5/-9). Pure for tests.\n/// A net-LESS broker (`net_up == false`) surfaces \"no connection (retrying)\" +\n/// a pump waiting on the network — NOT a STALLED alarm read off a stale\n/// pre-boot heartbeat (the kitsubito 2026-06-08 boot-race misreport, where the\n/// heartbeat age exceeded uptime). With net up, the heartbeat drives\n\n[Showing lines 3859-4098 of 19062. Use :4099 to continue]"}],"details":{"truncation":{"content":"    }\r\n    // Leg 1: soft-stop — the listener marker goes first so nothing new is\r\n    // accepted while the edge runs.\r\n    let _ = std::fs::remove_file(perch::resolve_ready_file(&id, ParentHint::Infer));\r\n    // Leg 2: the suspend edge (echo gate + shell cascade live in the host).\r\n    // Leg-2 stays LOCAL_ONLY (REQ-REST-VERB-ROUTING): shutdown is an endpoint's\r\n    // OWN self-suspend — a bare-id miss must NOT route the verb to some other\r\n    // node's instance. Pass `false` so the A-3 remote fallback never engages.\r\n    cmd_rest(&id, spt_daemon::RestEvent::Suspend, \"SHUTDOWN\", false)\r\n}\r\n\r\n/// Run the per-machine daemon (`spt daemon run`, M8 decision 5). FOREGROUND +\r\n/// unelevated by contract (REQ-DAEMON-7): this process IS the daemon, blocking\r\n/// the broker + seed-control channels until signalled. Never auto-detaches —\r\n/// the background/de-elevated path lives in `start`.\r\n///\r\n/// Two guards before binding:\r\n/// - Windows + elevated ⇒ REFUSE (exit 2). An elevated `run` would otherwise\r\n///   respawn detached/de-elevated (KH 5.7) and vanish into the background.\r\n/// - A managed service is already active ⇒ warn loudly (running inline fights\r\n///   it for the socket — the kitsubito 2026-06-08 restart-loop).\r\n// [impl->REQ-DAEMON-3]\r\n// [impl->REQ-DAEMON-7]\r\n// [impl->REQ-CLI-2]\r\nfn cmd_daemon_run(detached: bool) -> i32 {\r\n    // Broker self-detach (REQ-HAZARD-WMI-DAEMON-WINDOW): when the auto-start ladder\r\n    // launched us with `--detached`, drop our console FIRST so the conhost the\r\n    // WMI/schtasks `start /b` rung allocated exits — before any output or bind. A\r\n    // human-typed foreground `spt daemon run` omits the flag and keeps its console.\r\n    #[cfg(windows)]\r\n    if detached {\r\n        spt_daemon::daemon::detach_console();\r\n    }\r\n    // Persist the broker's stderr to the rotating SPT_HOME/logs sink\r\n    // (REQ-DAEMON-STDERR-PERSIST) BEFORE the null-handles guard below: redirecting to\r\n    // a FILE first means the pipe-defense then sees a safe (non-pipe) file and leaves\r\n    // it alone — so a detached broker's diagnostics land on disk instead of /dev/null\r\n    // (the incident-night RCA-blind gap). The broker is the stable half (no brain\r\n    // generation) → stamp 0; the role tag distinguishes it. Best-effort.\r\n    // [impl->REQ-DAEMON-STDERR-PERSIST]\r\n    let _ = spt_daemon::stderrlog::install(\"broker\", 0);\r\n    // Defense-in-depth (REQ-HAZARD-DETACHED-DAEMON-STDIO): null the std handles when\r\n    // detached-in-fact (no console OR a piped stderr) EVEN IF `--detached` was not\r\n    // passed — the stale-bare-at-logon-task / bare-deelevated-respawn / any-unknown\r\n    // rung case. Runs AFTER detach_console (idempotent) and BEFORE any bind/output, so\r\n    // a filling inherited pipe can never block the daemon. A foreground human keeps\r\n    // their console (both checks false).\r\n    #[cfg(windows)]\r\n    spt_daemon::daemon::null_std_handles_if_detached_in_fact();\r\n    #[cfg(not(windows))]\r\n    let _ = detached; // unix detaches via setsid in spawn_detached; flag unused.\r\n    #[cfg(windows)]\r\n    if spt_daemon::service::run_refused_when_vanishing(\r\n        true,\r\n        spt_daemon::deelevate::has_deelevation_target(),\r\n    ) {\r\n        // Only the vanishing case: elevated WITH an unelevated desktop shell to\r\n        // drop to (run would respawn detached/de-elevated). A headless or\r\n        // uniformly-elevated session has no target and serves foreground below.\r\n        eprintln!(\r\n            \"DAEMON_RUN_ELEVATED: `daemon run` is foreground; from an elevated shell it would \\\r\n             respawn in the background — use `spt daemon start` for a background daemon, or run \\\r\n             from an unelevated shell\"\r\n        );\r\n        return 2;\r\n    }\r\n    if let Some(label) = spt_daemon::active_service_conflict() {\r\n        eprintln!(\r\n            \"DAEMON_RUN_CONFLICT: a managed {label} service is active; running inline will \\\r\n             fight it for the socket — `spt daemon stop` first, or use `spt daemon start`\"\r\n        );\r\n    }\r\n    match spt_daemon::Daemon::run() {\r\n        Ok(()) => 0,\r\n        Err(e) => {\r\n            eprintln!(\"DAEMON_EXIT: {e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// Run the brain child (`spt daemon brain`, hidden — ADR-0018 D1). The broker\r\n/// process (`daemon run`) spawns + supervises this; it is not a user-facing\r\n/// entry. FOREGROUND: this process IS the brain, blocking until the broker\r\n/// connection drops (then it exits for a supervised respawn). `generation` +\r\n/// `start_reason` are the broker's spawn-time stamp (ADR-0018 V2), defaulted so\r\n/// an older broker's bare argv still parses (KH-2.3).\r\n// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\r\nfn cmd_daemon_brain(generation: u64, start_reason: &str) -> i32 {\r\n    match spt_daemon::Daemon::run_brain(generation, start_reason) {\r\n        Ok(()) => 0,\r\n        Err(e) => {\r\n            eprintln!(\"BRAIN_EXIT: {e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// `spt daemon start` (REQ-DAEMON-6) — ensure the daemon is up in the\r\n/// background, idempotent + service-aware: a registered OS service is driven\r\n/// via its manager (never a competing manual daemon), else a detached spawn.\r\n// [impl->REQ-DAEMON-6]\r\nfn cmd_daemon_start() -> i32 {\r\n    match spt_daemon::start_daemon() {\r\n        Ok(spt_daemon::StartOutcome::AlreadyRunning) => {\r\n            eprintln!(\"DAEMON_ALREADY_RUNNING\");\r\n            0\r\n        }\r\n        Ok(spt_daemon::StartOutcome::ServiceStarted(label)) => {\r\n            eprintln!(\"DAEMON_STARTED (via {label})\");\r\n            0\r\n        }\r\n        Ok(spt_daemon::StartOutcome::Spawned) => {\r\n            eprintln!(\"DAEMON_STARTED\");\r\n            0\r\n        }\r\n        Err(e) => {\r\n            eprintln!(\"DAEMON_START_FAIL: {e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// `spt daemon stop` — graceful, service-aware shutdown (REQ-DAEMON-6, M8\r\n/// decision 5 / REQ-CLI-2): a managed service is stopped through its manager\r\n/// (so it does not auto-restart-fight); a manual daemon acks the seed-control\r\n/// stop then exits. Idempotent UX: not-running is already-stopped (exit 0).\r\n/// The live-session stop guard (REQ-DAEMON-STOP-LIVE-SESSION-WARN): pure — a daemon\r\n/// hosting live sessions REFUSES to stop (naming them) unless `--force`, so a stop\r\n/// never silently kills hosted endpoints out from under an operator. Empty set (no\r\n/// hosted sessions) or `--force` ⇒ proceed. `Err` carries the operator warning.\r\n// [impl->REQ-DAEMON-STOP-LIVE-SESSION-WARN]\r\nfn stop_live_session_guard(live_sessions: &[String], force: bool) -> Result<(), String> {\r\n    if live_sessions.is_empty() || force {\r\n        return Ok(());\r\n    }\r\n    Err(format!(\r\n        \"the daemon is hosting {} live session(s) that stopping would kill: {}. \\\r\n         Re-run with `spt daemon stop --force` to stop anyway — they come back on the \\\r\n         next `spt daemon start` — or leave the daemon running.\",\r\n        live_sessions.len(),\r\n        live_sessions.join(\", \")\r\n    ))\r\n}\r\n\r\n/// The endpoint ids the running daemon currently hosts a broker PTY session for —\r\n/// the stop-guard's \"would be killed\" set. Best-effort: a down/unreachable daemon\r\n/// yields none (the guard is then a no-op and the normal stop path reports\r\n/// already-stopped). // [impl->REQ-DAEMON-STOP-LIVE-SESSION-WARN]\r\nfn live_hosted_session_ids() -> Vec<String> {\r\n    if !spt_daemon::is_running() {\r\n        return Vec::new();\r\n    }\r\n    match spt_daemon::brain::Brain::cold_start(&spt_daemon::broker_socket_name(), now_ms()) {\r\n        Ok(mut b) => b\r\n            .sessions()\r\n            .map(|r| {\r\n                r.sessions\r\n                    .into_iter()\r\n                    .map(|s| s.endpoint)\r\n                    .filter(|e| !e.is_empty())\r\n                    .collect()\r\n            })\r\n            .unwrap_or_default(),\r\n        Err(_) => Vec::new(),\r\n    }\r\n}\r\n\r\n// [impl->REQ-DAEMON-6]\r\n// [impl->REQ-CLI-2]\r\n// [impl->REQ-DAEMON-STOP-LIVE-SESSION-WARN]\r\nfn cmd_daemon_stop(force: bool) -> i32 {\r\n    // Guard: refuse + name the sessions if stopping would kill live hosted endpoints,\r\n    // unless --force. Runs BEFORE the stop so nothing is torn down on a refusal.\r\n    let live = live_hosted_session_ids();\r\n    if let Err(msg) = stop_live_session_guard(&live, force) {\r\n        eprintln!(\"DAEMON_STOP_REFUSED: {msg}\");\r\n        return 3;\r\n    }\r\n    match spt_daemon::stop_daemon() {\r\n        Ok(spt_daemon::StopOutcome::NotRunning) => {\r\n            eprintln!(\"DAEMON_NOT_RUNNING (already stopped)\");\r\n            0\r\n        }\r\n        Ok(spt_daemon::StopOutcome::ServiceStopped(label)) => {\r\n            eprintln!(\"DAEMON_STOPPED (via {label})\");\r\n            0\r\n        }\r\n        Ok(spt_daemon::StopOutcome::IpcStopped) => {\r\n            eprintln!(\"DAEMON_STOPPED\");\r\n            0\r\n        }\r\n        Err(e) => {\r\n            eprintln!(\"DAEMON_STOP_FAIL: {e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// `spt daemon refresh` — cycle the daemon's brain in place with NO binary swap\r\n/// (REQ-DAEMON-REFRESH): the apply_staged brain-cycle path minus the swap, so the\r\n/// broker and every hosted PTY survive by construction. Requires a running\r\n/// daemon (nothing to refresh otherwise — `daemon start` is the cold path). A\r\n/// broker without a supervisor wired accepts but cannot act; that is loud, not\r\n/// silent — the operator asked for a cycle that did not happen.\r\n// [impl->REQ-DAEMON-REFRESH]\r\nfn cmd_daemon_refresh() -> i32 {\r\n    if !spt_daemon::is_running() {\r\n        eprintln!(\"DAEMON_NOT_RUNNING: nothing to refresh — `spt daemon start` brings it up fresh\");\r\n        return 1;\r\n    }\r\n    match spt_daemon::refresh_brain(&spt_daemon::broker_socket_name()) {\r\n        Ok(true) => {\r\n            println!(\r\n                \"Daemon coordinator restarted in place — hosted sessions and connections were \\\r\n                 not touched. Run `spt daemon status` to confirm.\"\r\n            );\r\n            0\r\n        }\r\n        Ok(false) => {\r\n            eprintln!(\r\n                \"DAEMON_REFRESH_NOT_HONORED: the daemon accepted the request but has no \\\r\n                 supervisor to cycle its coordinator (an older or unmanaged daemon) — use \\\r\n                 `spt daemon stop` then `spt daemon start` instead (hosted sessions come back \\\r\n                 automatically).\"\r\n            );\r\n            1\r\n        }\r\n        Err(e) => {\r\n            eprintln!(\"DAEMON_REFRESH_FAIL:{e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// The connection + peer-pump status lines (REQ-DAEMON-5/-9). Pure for tests.\r\n/// A net-LESS broker (`net_up == false`) surfaces \"no connection (retrying)\" +\r\n/// a pump waiting on the network — NOT a STALLED alarm read off a stale\r\n/// pre-boot heartbeat (the kitsubito 2026-06-08 boot-race misreport, where the\r\n/// heartbeat age exceeded uptime). With net up, the heartbeat drives\r","truncated":true,"truncatedBy":"lines","totalLines":15204,"totalBytes":10901,"outputLines":240,"outputBytes":10901,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn cmd_shutdown(id: Option<String>) -> i32 {\n…\n    if id.contains('@') || id.contains(':') {\n…\n    }\r\n    // Leg 1: soft-stop — the listener marker goes first so nothing new is\r\n    // accepted while the edge runs.\r\n    let _ = std::fs::remove_file(perch::resolve_ready_file(&id, ParentHint::Infer));\r\n    // Leg 2: the suspend edge (echo gate + shell cascade live in the host).\r\n    // Leg-2 stays LOCAL_ONLY (REQ-REST-VERB-ROUTING): shutdown is an endpoint's\r\n    // OWN self-suspend — a bare-id miss must NOT route the verb to some other\r\n    // node's instance. Pass `false` so the A-3 remote fallback never engages.\r\n    cmd_rest(&id, spt_daemon::RestEvent::Suspend, \"SHUTDOWN\", false)\r\n}\r\n\r\n/// Run the per-machine daemon (`spt daemon run`, M8 decision 5). FOREGROUND +\r\n/// unelevated by contract (REQ-DAEMON-7): this process IS the daemon, blocking\r\n/// the broker + seed-control channels until signalled. Never auto-detaches —\r\n/// the background/de-elevated path lives in `start`.\r\n///\r\n/// Two guards before binding:\r\n/// - Windows + elevated ⇒ REFUSE (exit 2). An elevated `run` would otherwise\r\n///   respawn detached/de-elevated (KH 5.7) and vanish into the background.\r\n/// - A managed service is already active ⇒ warn loudly (running inline fights\r\n///   it for the socket — the kitsubito 2026-06-08 restart-loop).\r\n// [impl->REQ-DAEMON-3]\r\n// [impl->REQ-DAEMON-7]\r\n// [impl->REQ-CLI-2]\r\nfn cmd_daemon_run(detached: bool) -> i32 {\r\n    // Broker self-detach (REQ-HAZARD-WMI-DAEMON-WINDOW): when the auto-start ladder\r\n    // launched us with `--detached`, drop our console FIRST so the conhost the\r\n    // WMI/schtasks `start /b` rung allocated exits — before any output or bind. A\r\n    // human-typed foreground `spt daemon run` omits the flag and keeps its console.\r\n    #[cfg(windows)]\r\n    if detached {\r\n        spt_daemon::daemon::detach_console();\r\n    }\r\n    // Persist the broker's stderr to the rotating SPT_HOME/logs sink\r\n    // (REQ-DAEMON-STDERR-PERSIST) BEFORE the null-handles guard below: redirecting to\r\n    // a FILE first means the pipe-defense then sees a safe (non-pipe) file and leaves\r\n    // it alone — so a detached broker's diagnostics land on disk instead of /dev/null\r\n    // (the incident-night RCA-blind gap). The broker is the stable half (no brain\r\n    // generation) → stamp 0; the role tag distinguishes it. Best-effort.\r\n    // [impl->REQ-DAEMON-STDERR-PERSIST]\r\n    let _ = spt_daemon::stderrlog::install(\"broker\", 0);\r\n    // Defense-in-depth (REQ-HAZARD-DETACHED-DAEMON-STDIO): null the std handles when\r\n    // detached-in-fact (no console OR a piped stderr) EVEN IF `--detached` was not\r\n    // passed — the stale-bare-at-logon-task / bare-deelevated-respawn / any-unknown\r\n    // rung case. Runs AFTER detach_console (idempotent) and BEFORE any bind/output, so\r\n    // a filling inherited pipe can never block the daemon. A foreground human keeps\r\n    // their console (both checks false).\r\n    #[cfg(windows)]\r\n    spt_daemon::daemon::null_std_handles_if_detached_in_fact();\r\n    #[cfg(not(windows))]\r\n    let _ = detached; // unix detaches via setsid in spawn_detached; flag unused.\r\n    #[cfg(windows)]\r\n    if spt_daemon::service::run_refused_when_vanishing(\r\n        true,\r\n        spt_daemon::deelevate::has_deelevation_target(),\r\n    ) {\r\n        // Only the vanishing case: elevated WITH an unelevated desktop shell to\r\n        // drop to (run would respawn detached/de-elevated). A headless or\r\n        // uniformly-elevated session has no target and serves foreground below.\r\n        eprintln!(\r\n            \"DAEMON_RUN_ELEVATED: `daemon run` is foreground; from an elevated shell it would \\\r\n             respawn in the background — use `spt daemon start` for a background daemon, or run \\\r\n             from an unelevated shell\"\r\n        );\r\n        return 2;\r\n    }\r\n    if let Some(label) = spt_daemon::active_service_conflict() {\r\n        eprintln!(\r\n            \"DAEMON_RUN_CONFLICT: a managed {label} service is active; running inline will \\\r\n             fight it for the socket — `spt daemon stop` first, or use `spt daemon start`\"\r\n        );\r\n    }\r\n    match spt_daemon::Daemon::run() {\r\n        Ok(()) => 0,\r\n        Err(e) => {\r\n            eprintln!(\"DAEMON_EXIT: {e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// Run the brain child (`spt daemon brain`, hidden — ADR-0018 D1). The broker\r\n/// process (`daemon run`) spawns + supervises this; it is not a user-facing\r\n/// entry. FOREGROUND: this process IS the brain, blocking until the broker\r\n/// connection drops (then it exits for a supervised respawn). `generation` +\r\n/// `start_reason` are the broker's spawn-time stamp (ADR-0018 V2), defaulted so\r\n/// an older broker's bare argv still parses (KH-2.3).\r\n// [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\r\nfn cmd_daemon_brain(generation: u64, start_reason: &str) -> i32 {\r\n    match spt_daemon::Daemon::run_brain(generation, start_reason) {\r\n        Ok(()) => 0,\r\n        Err(e) => {\r\n            eprintln!(\"BRAIN_EXIT: {e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// `spt daemon start` (REQ-DAEMON-6) — ensure the daemon is up in the\r\n/// background, idempotent + service-aware: a registered OS service is driven\r\n/// via its manager (never a competing manual daemon), else a detached spawn.\r\n// [impl->REQ-DAEMON-6]\r\nfn cmd_daemon_start() -> i32 {\r\n    match spt_daemon::start_daemon() {\r\n        Ok(spt_daemon::StartOutcome::AlreadyRunning) => {\r\n            eprintln!(\"DAEMON_ALREADY_RUNNING\");\r\n            0\r\n        }\r\n        Ok(spt_daemon::StartOutcome::ServiceStarted(label)) => {\r\n            eprintln!(\"DAEMON_STARTED (via {label})\");\r\n            0\r\n        }\r\n        Ok(spt_daemon::StartOutcome::Spawned) => {\r\n            eprintln!(\"DAEMON_STARTED\");\r\n            0\r\n        }\r\n        Err(e) => {\r\n            eprintln!(\"DAEMON_START_FAIL: {e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// `spt daemon stop` — graceful, service-aware shutdown (REQ-DAEMON-6, M8\r\n/// decision 5 / REQ-CLI-2): a managed service is stopped through its manager\r\n/// (so it does not auto-restart-fight); a manual daemon acks the seed-control\r\n/// stop then exits. Idempotent UX: not-running is already-stopped (exit 0).\r\n/// The live-session stop guard (REQ-DAEMON-STOP-LIVE-SESSION-WARN): pure — a daemon\r\n/// hosting live sessions REFUSES to stop (naming them) unless `--force`, so a stop\r\n/// never silently kills hosted endpoints out from under an operator. Empty set (no\r\n/// hosted sessions) or `--force` ⇒ proceed. `Err` carries the operator warning.\r\n// [impl->REQ-DAEMON-STOP-LIVE-SESSION-WARN]\r\nfn stop_live_session_guard(live_sessions: &[String], force: bool) -> Result<(), String> {\r\n    if live_sessions.is_empty() || force {\r\n        return Ok(());\r\n    }\r\n    Err(format!(\r\n        \"the daemon is hosting {} live session(s) that stopping would kill: {}. \\\r\n         Re-run with `spt daemon stop --force` to stop anyway — they come back on the \\\r\n         next `spt daemon start` — or leave the daemon running.\",\r\n        live_sessions.len(),\r\n        live_sessions.join(\", \")\r\n    ))\r\n}\r\n\r\n/// The endpoint ids the running daemon currently hosts a broker PTY session for —\r\n/// the stop-guard's \"would be killed\" set. Best-effort: a down/unreachable daemon\r\n/// yields none (the guard is then a no-op and the normal stop path reports\r\n/// already-stopped). // [impl->REQ-DAEMON-STOP-LIVE-SESSION-WARN]\r\nfn live_hosted_session_ids() -> Vec<String> {\r\n    if !spt_daemon::is_running() {\r\n        return Vec::new();\r\n    }\r\n    match spt_daemon::brain::Brain::cold_start(&spt_daemon::broker_socket_name(), now_ms()) {\r\n        Ok(mut b) => b\r\n            .sessions()\r\n            .map(|r| {\r\n                r.sessions\r\n                    .into_iter()\r\n                    .map(|s| s.endpoint)\r\n                    .filter(|e| !e.is_empty())\r\n                    .collect()\r\n            })\r\n            .unwrap_or_default(),\r\n        Err(_) => Vec::new(),\r\n    }\r\n}\r\n\r\n// [impl->REQ-DAEMON-6]\r\n// [impl->REQ-CLI-2]\r\n// [impl->REQ-DAEMON-STOP-LIVE-SESSION-WARN]\r\nfn cmd_daemon_stop(force: bool) -> i32 {\r\n    // Guard: refuse + name the sessions if stopping would kill live hosted endpoints,\r\n    // unless --force. Runs BEFORE the stop so nothing is torn down on a refusal.\r\n    let live = live_hosted_session_ids();\r\n    if let Err(msg) = stop_live_session_guard(&live, force) {\r\n        eprintln!(\"DAEMON_STOP_REFUSED: {msg}\");\r\n        return 3;\r\n    }\r\n    match spt_daemon::stop_daemon() {\r\n        Ok(spt_daemon::StopOutcome::NotRunning) => {\r\n            eprintln!(\"DAEMON_NOT_RUNNING (already stopped)\");\r\n            0\r\n        }\r\n        Ok(spt_daemon::StopOutcome::ServiceStopped(label)) => {\r\n            eprintln!(\"DAEMON_STOPPED (via {label})\");\r\n            0\r\n        }\r\n        Ok(spt_daemon::StopOutcome::IpcStopped) => {\r\n            eprintln!(\"DAEMON_STOPPED\");\r\n            0\r\n        }\r\n        Err(e) => {\r\n            eprintln!(\"DAEMON_STOP_FAIL: {e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// `spt daemon refresh` — cycle the daemon's brain in place with NO binary swap\r\n/// (REQ-DAEMON-REFRESH): the apply_staged brain-cycle path minus the swap, so the\r\n/// broker and every hosted PTY survive by construction. Requires a running\r\n/// daemon (nothing to refresh otherwise — `daemon start` is the cold path). A\r\n/// broker without a supervisor wired accepts but cannot act; that is loud, not\r\n/// silent — the operator asked for a cycle that did not happen.\r\n// [impl->REQ-DAEMON-REFRESH]\r\nfn cmd_daemon_refresh() -> i32 {\r\n    if !spt_daemon::is_running() {\r\n        eprintln!(\"DAEMON_NOT_RUNNING: nothing to refresh — `spt daemon start` brings it up fresh\");\r\n        return 1;\r\n    }\r\n    match spt_daemon::refresh_brain(&spt_daemon::broker_socket_name()) {\r\n        Ok(true) => {\r\n            println!(\r\n                \"Daemon coordinator restarted in place — hosted sessions and connections were \\\r\n                 not touched. Run `spt daemon status` to confirm.\"\r\n            );\r\n            0\r\n        }\r\n        Ok(false) => {\r\n            eprintln!(\r\n                \"DAEMON_REFRESH_NOT_HONORED: the daemon accepted the request but has no \\\r\n                 supervisor to cycle its coordinator (an older or unmanaged daemon) — use \\\r\n                 `spt daemon stop` then `spt daemon start` instead (hosted sessions come back \\\r\n                 automatically).\"\r\n            );\r\n            1\r\n        }\r\n        Err(e) => {\r\n            eprintln!(\"DAEMON_REFRESH_FAIL:{e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// The connection + peer-pump status lines (REQ-DAEMON-5/-9). Pure for tests.\r\n/// A net-LESS broker (`net_up == false`) surfaces \"no connection (retrying)\" +\r\n/// a pump waiting on the network — NOT a STALLED alarm read off a stale\r\n/// pre-boot heartbeat (the kitsubito 2026-06-08 boot-race misreport, where the\r\n/// heartbeat age exceeded uptime). With net up, the heartbeat drives\r","startLine":3843,"lineNumbers":[3843,null,3850,null,3859,3860,3861,3862,3863,3864,3865,3866,3867,3868,3869,3870,3871,3872,3873,3874,3875,3876,3877,3878,3879,3880,3881,3882,3883,3884,3885,3886,3887,3888,3889,3890,3891,3892,3893,3894,3895,3896,3897,3898,3899,3900,3901,3902,3903,3904,3905,3906,3907,3908,3909,3910,3911,3912,3913,3914,3915,3916,3917,3918,3919,3920,3921,3922,3923,3924,3925,3926,3927,3928,3929,3930,3931,3932,3933,3934,3935,3936,3937,3938,3939,3940,3941,3942,3943,3944,3945,3946,3947,3948,3949,3950,3951,3952,3953,3954,3955,3956,3957,3958,3959,3960,3961,3962,3963,3964,3965,3966,3967,3968,3969,3970,3971,3972,3973,3974,3975,3976,3977,3978,3979,3980,3981,3982,3983,3984,3985,3986,3987,3988,3989,3990,3991,3992,3993,3994,3995,3996,3997,3998,3999,4000,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4029,4030,4031,4032,4033,4034,4035,4036,4037,4038,4039,4040,4041,4042,4043,4044,4045,4046,4047,4048,4049,4050,4051,4052,4053,4054,4055,4056,4057,4058,4059,4060,4061,4062,4063,4064,4065,4066,4067,4068,4069,4070,4071,4072,4073,4074,4075,4076,4077,4078,4079,4080,4081,4082,4083,4084,4085,4086,4087,4088,4089,4090,4091,4092,4093,4094,4095,4096,4097,4098]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\cli.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":19062,"totalBytes":10901,"outputLines":240,"outputBytes":10901,"shownRange":{"start":3859,"end":4098},"nextOffset":4099}}},"isError":false,"timestamp":1784247426705}}
{"type":"message","id":"e4bab337","parentId":"a50f2299","timestamp":"2026-07-17T00:17:14.179Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing finish behavior values**"},{"type":"toolCall","id":"call_153rGpeB7XkCW3vT1Z2CbJwG|fc_099a7567dadef391016a59748a22dc819bbe69a6b415e8840b","name":"read","arguments":{"i":"Reading composite update flow","path":"crates/spt/src/cli.rs","selector":"5038-5135,5215-5295"}},{"type":"toolCall","id":"call_tUq9UsuueGKMS47u1OaWz8ar|fc_099a7567dadef391016a59748a22e8819b9bf3710521e206b2","name":"read","arguments":{"i":"Reading reconnect contract","path":"crates/spt/src/rc.rs","selector":"1560-1660"}},{"type":"toolCall","id":"call_YS3wCcW6q1DO0AxsUS8AalI4|fc_099a7567dadef391016a59748a22f0819bb9a2d0c9a42b250b","name":"read","arguments":{"i":"Reading restart survival test","path":"crates/spt-daemon/tests/endpoint_survival.rs","selector":"1-240"}},{"type":"toolCall","id":"call_aQYXcmsbYCSwf0xbQH7i86jW|fc_099a7567dadef391016a59748a22f4819b9eed0a621073235a","name":"read","arguments":{"i":"Reading brain swap survival test","path":"crates/spt/tests/brain_survive.rs","selector":"180-380"}},{"type":"toolCall","id":"call_5t02I449v2sqDhtOjrOHYKl3|fc_099a7567dadef391016a59748a22fc819b8a437ebc07205efe","name":"grep","arguments":{"i":"Finding multi-session refresh tests","pattern":"refresh|restart|generation","path":"crates/spt-daemon/tests; crates/spt/tests","selector":null,"case":false,"gitignore":true,"skip":20}},{"type":"toolCall","id":"call_oz5x5LdbkEVCdhencwlvorLZ|fc_099a7567dadef391016a59748a2304819b88d3478486352189","name":"grep","arguments":{"i":"Finding five-session coverage","pattern":"for .*0\\.\\.[45]|0\\.\\.5|five|5 sessions|spawn.*session.*spawn|multi.session|N.*session","path":"crates/spt-daemon/tests; crates/spt/tests","selector":null,"case":false,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":30994,"output":323,"cacheRead":138752,"cacheWrite":0,"totalTokens":170069,"reasoningTokens":13,"cost":{"input":0.15497000000000002,"output":0.00969,"cacheRead":0.069376,"cacheWrite":0,"total":0.23403600000000002}},"stopReason":"toolUse","timestamp":1784247426752,"responseId":"resp_099a7567dadef391016a597482e85c819b9db0171546a245f6","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a597484466c819b865ec8e3dd49dc11","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXSKqvGdvky7ZTY9Hm8nJXXMhesdHqWk2_d5OPMXhKyXw2eKW59nlGgWJ9oPZc8u6535Wx0mehFrDrkoJQIdL-iFSUQmfeJs1TO39-Pt7qG5HOsBaCopY1QFdrbI42RYShjT08iQQeyofpse6VKp7ogHdjsN-AR__w96HZmEeMb5xGTmQlMNZDalznxM_cvEeN-34dw0sl8_98fx_cNCjVsDMM8OjpKmmODFx0Cn68G93Ia28D1g0crBXNGgodeAw71nmOS7MeQwE8_kNvvfozWZJEw_pcuxvW5Y3flGaPZIgXbCMyZwAGpt-kHCVfCCsIsZcuoklAFVg0fnRuUhMSKybY161BKXluPg2dmAwvKZk-FAiYVv05PacvZxSqT5QZNRaKrRRahvUIWqOeLIDoUd4RbyrYb0-oYch9Lz-BSFIpgP1RdHop7-JjjBuvKjKVCMpHDHXN3VAd2XoOvG5HeY0drE5tghSCxEuudVONZUqnmCgB3hzapjKbVCrG-LSd2oqdNQZKp7UqgPfGhW1htpfwkdVxjQaQZuGAyW54T7-GYGsn1V57Po5IRQ0C9dsbhgmaragkf8QjYU8d9NpBSsQ3W28OxA9AmVitQi563Drmz0xsUyrQJCuBbYp2TtChajdjDr98d0haYfp4Jjc9pcthnH4MD3HJFxY5OSruMmtUNvuYmgCVnG7QrkvS98qO0WQ3IztHi5kV8tnf29LGvCQTF3RbwfkNp_ILMNiSWK9GX7r27Xd0N1p-MySW6b8t1LgDJKSf0YVJRuZCGjh__9NHstngcPU03v6b5r_PsS3kuNPoa_g_cmFVbESBTKfosD_tOmZ6Lx6KOBaDYeB8rDw0zdBu2quiFYsyUXldhsW3sPzULVz6xn0KCrd-nfQnpg3Dt_yu8-KGLfgTFK4c5v00Wab1GoFeJH3qijmzp2TTrKOCV86b7-AH0JT7AnPkrP1lizpvRdN9qzuCl1eMiu2g==","summary":[{"type":"summary_text","text":"**Analyzing finish behavior values**"}]},{"id":"fc_099a7567dadef391016a59748a22dc819bbe69a6b415e8840b","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading composite update flow\",\"path\":\"crates/spt/src/cli.rs\",\"selector\":\"5038-5135,5215-5295\"}","call_id":"call_153rGpeB7XkCW3vT1Z2CbJwG","name":"read"},{"id":"fc_099a7567dadef391016a59748a22e8819b9bf3710521e206b2","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading reconnect contract\",\"path\":\"crates/spt/src/rc.rs\",\"selector\":\"1560-1660\"}","call_id":"call_tUq9UsuueGKMS47u1OaWz8ar","name":"read"},{"id":"fc_099a7567dadef391016a59748a22f0819bb9a2d0c9a42b250b","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading restart survival test\",\"path\":\"crates/spt-daemon/tests/endpoint_survival.rs\",\"selector\":\"1-240\"}","call_id":"call_YS3wCcW6q1DO0AxsUS8AalI4","name":"read"},{"id":"fc_099a7567dadef391016a59748a22f4819b9eed0a621073235a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading brain swap survival test\",\"path\":\"crates/spt/tests/brain_survive.rs\",\"selector\":\"180-380\"}","call_id":"call_aQYXcmsbYCSwf0xbQH7i86jW","name":"read"},{"id":"fc_099a7567dadef391016a59748a22fc819b8a437ebc07205efe","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding multi-session refresh tests\",\"pattern\":\"refresh|restart|generation\",\"path\":\"crates/spt-daemon/tests; crates/spt/tests\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":20}","call_id":"call_5t02I449v2sqDhtOjrOHYKl3","name":"grep"},{"id":"fc_099a7567dadef391016a59748a2304819b88d3478486352189","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding five-session coverage\",\"pattern\":\"for .*0\\\\.\\\\.[45]|0\\\\.\\\\.5|five|5 sessions|spawn.*session.*spawn|multi.session|N.*session\",\"path\":\"crates/spt-daemon/tests; crates/spt/tests\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_oz5x5LdbkEVCdhencwlvorLZ","name":"grep"}]},"duration":7426.071500003338,"ttft":1515.4901999980211,"contextSnapshot":{"promptTokens":169746,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_153rGpeB7XkCW3vT1Z2CbJwG|fc_099a7567dadef391016a59748a22dc819bbe69a6b415e8840b","toolName":"read","startedAt":"2026-07-17T00:17:14.180Z","args":{"path":"crates/spt/src/cli.rs"},"intent":"Reading composite update flow"},"id":"42af5d48","parentId":"e4bab337","timestamp":"2026-07-17T00:17:14.180Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_tUq9UsuueGKMS47u1OaWz8ar|fc_099a7567dadef391016a59748a22e8819b9bf3710521e206b2","toolName":"read","startedAt":"2026-07-17T00:17:14.180Z","args":{"path":"crates/spt/src/rc.rs"},"intent":"Reading reconnect contract"},"id":"7734bca2","parentId":"42af5d48","timestamp":"2026-07-17T00:17:14.180Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_YS3wCcW6q1DO0AxsUS8AalI4|fc_099a7567dadef391016a59748a22f0819bb9a2d0c9a42b250b","toolName":"read","startedAt":"2026-07-17T00:17:14.180Z","args":{"path":"crates/spt-daemon/tests/endpoint_survival.rs"},"intent":"Reading restart survival test"},"id":"fb325742","parentId":"7734bca2","timestamp":"2026-07-17T00:17:14.180Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_aQYXcmsbYCSwf0xbQH7i86jW|fc_099a7567dadef391016a59748a22f4819b9eed0a621073235a","toolName":"read","startedAt":"2026-07-17T00:17:14.180Z","args":{"path":"crates/spt/tests/brain_survive.rs"},"intent":"Reading brain swap survival test"},"id":"36b8cede","parentId":"fb325742","timestamp":"2026-07-17T00:17:14.180Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_5t02I449v2sqDhtOjrOHYKl3|fc_099a7567dadef391016a59748a22fc819b8a437ebc07205efe","toolName":"grep","startedAt":"2026-07-17T00:17:14.180Z","args":{"path":"crates/spt-daemon/tests; crates/spt/tests"},"intent":"Finding multi-session refresh tests"},"id":"3d2b7ded","parentId":"36b8cede","timestamp":"2026-07-17T00:17:14.180Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_oz5x5LdbkEVCdhencwlvorLZ|fc_099a7567dadef391016a59748a2304819b88d3478486352189","toolName":"grep","startedAt":"2026-07-17T00:17:14.180Z","args":{"path":"crates/spt-daemon/tests; crates/spt/tests"},"intent":"Finding five-session coverage"},"id":"aa847d8b","parentId":"3d2b7ded","timestamp":"2026-07-17T00:17:14.180Z"}
{"type":"message","id":"3ddfd1ea","parentId":"aa847d8b","timestamp":"2026-07-17T00:17:14.327Z","message":{"role":"toolResult","toolCallId":"call_153rGpeB7XkCW3vT1Z2CbJwG|fc_099a7567dadef391016a59748a22dc819bbe69a6b415e8840b","toolName":"read","content":[{"type":"text","text":"fn cmd_update_apply(finish: bool) -> i32 {\n…\n    match outcome {\n…\n        Err(e) => {\n…\n            1\n        }\n    }\n}\n\n/// One leg of the bare `spt update` composite (REQ-UPDATE-DEFAULT-COMPOSITE,\n/// REQ-UPDATE-RESTART-SAFE-SWAP).\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\nenum UpdateLeg {\n    /// `update fetch --apply` — stage + install the core in one step (the\n    /// brain-only cycle: the invoking process survives by construction).\n    FetchApply,\n    /// `update fetch` — stage only; the swap waits for the Finish leg.\n    Fetch,\n    /// `update adapters` — sweep every gh_release adapter.\n    Adapters,\n    /// `update apply --finish` — swap + full daemon restart. LETHAL: from an\n    /// spt-hosted session the invoker's PTY dies here, so this leg is always\n    /// planned LAST (everything else has completed by then).\n    Finish,\n}\n\n/// Plan the composite's leg sequence — pure, unit-gated. The invariants:\n/// core before adapters (core-first doctrine); `--core-only` drops the\n/// adapters leg; `--restart` replaces the in-place apply with stage-now\n/// (`Fetch`) + `Finish` LAST (lethal-leg-last, operator-ruled: the whole-daemon\n/// restart must be the final act so every other leg completes from ANY\n/// invoking context, including a hosted session whose PTY the finish kills).\n// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\nfn plan_update_legs(core_only: bool, restart: bool) -> Vec<UpdateLeg> {\n    let mut legs = Vec::new();\n    legs.push(if restart {\n        UpdateLeg::Fetch\n    } else {\n        UpdateLeg::FetchApply\n    });\n    if !core_only {\n        legs.push(UpdateLeg::Adapters);\n    }\n    if restart {\n        legs.push(UpdateLeg::Finish);\n    }\n    legs\n}\n\n/// Bare `spt update` — the composite front door (REQ-UPDATE-DEFAULT-COMPOSITE):\n/// core first (`fetch --apply`; an already-current core no-ops and the run\n/// becomes adapters-only), then `update adapters`. The invoker survives the\n/// core leg by construction — apply cycles the BRAIN only, the broker and every\n/// PTY keep running (applyhost::apply_staged) — so no re-run machinery exists.\n/// With `--restart` (REQ-UPDATE-RESTART-SAFE-SWAP) the plan is fetch → adapters\n/// → `apply --finish` LAST: the one-step safe full-cycle swap.\n///\n/// Failure policy: a CORE-leg failure (fetch/apply refused, gh missing…)\n/// ABORTS the remaining legs — updating adapters against a channel that just\n/// refused is compounding, and a `--restart` finish after a failed fetch would\n/// bounce the daemon for nothing. An ADAPTERS-leg failure is isolated (that is\n/// its contract): loud, nonzero at exit, but the composite proceeds — under\n/// `--restart` the finish still completes the core cycle. The accepted\n/// asymmetry (new adapters briefly on old-activated core if the finish itself\n/// fails) is loud and operator-attended by nature of the flag.\n// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\nfn cmd_update_composite(core_only: bool, restart: bool) -> i32 {\n    let mut worst = 0;\n    for leg in plan_update_legs(core_only, restart) {\n        let code = match leg {\n            UpdateLeg::FetchApply => cmd_update_fetch(None, None, true),\n            UpdateLeg::Fetch => cmd_update_fetch(None, None, false),\n            UpdateLeg::Adapters => cmd_adapter_update(&spt_store::perch::adapters_dir(), None),\n            UpdateLeg::Finish => cmd_update_apply(true),\n        };\n        if code != 0 {\n            if composite_abort_on_failure(leg) {\n                eprintln!(\n                    \"UPDATE_COMPOSITE_ABORTED: the core update failed — the remaining \\\n                     leg(s) were not run\"\n                );\n                return code;\n            }\n            worst = code;\n        }\n    }\n    worst\n}\n\n/// Whether a failed composite leg aborts the remaining legs — pure, unit-gated.\n/// Only the CORE legs abort (a refused channel poisons the adapters leg, and a\n/// finish after a failed fetch is a pointless daemon bounce); an adapters-leg\n/// failure is isolated by contract and the composite proceeds.\n// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\nfn composite_abort_on_failure(leg: UpdateLeg) -> bool {\n    matches!(leg, UpdateLeg::FetchApply | UpdateLeg::Fetch)\n}\n\n/// Land the staged docs bundle at `$SPT_HOME/docs` — the single current copy,\n/// version-matched to the just-applied release (ADR-0036 §4). Extraction is a\n…\nfn land_staged_docs(cache: &spt_daemon::ReleaseCache) {\n…\n    if let Err(e) = std::fs::rename(&staging, &docs_dir) {\n…\n        let _ = std::fs::remove_dir_all(&staging);\n        eprintln!(\"UPDATE_DOCS_SKIPPED: promote docs: {e} — docs retry next fetch\");\n        return;\n    }\n    let _ = std::fs::remove_dir_all(&old);\n    // Landed — the staged bundle has served its purpose.\n    cache.clear_staged_docs();\n    println!(\"UPDATE_DOCS_LANDED: {}\", docs_dir.display());\n}\n\n/// `spt update apply --finish` — swap the bytes daemonless (never leaving an old\n/// coordinator running new-agent code), then RESTART the daemon onto them so the\n/// whole node runs the new version in one command. The restart rides\n/// REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL: `daemon start` re-runs the previously\n/// online hosted endpoints, so the cycle is not a massacre.\n// [impl->REQ-UPDATE-ONE-SHOT-FINISH]\nfn cmd_update_apply_finish(\n    cache: &spt_daemon::ReleaseCache,\n    keys: &std::path::Path,\n    exe: &std::path::Path,\n) -> i32 {\n    use spt_daemon::ApplyStagedOutcome as O;\n\n    // 1. Swap the bytes with NO broker touched. A daemonless swap can never leave\n    //    a running old broker paired with new brain bytes.\n    let applied = match spt_daemon::apply_staged_daemonless(cache, keys, exe) {\n        Ok(O::NoUpdate) => {\n            println!(\"NO_UPDATE\");\n            return 0;\n        }\n        // The proceed set — bytes are (or already were) on disk; go restart onto them.\n        Ok(o @ O::AppliedDaemonless { .. })\n        | Ok(o @ O::AppliedPending { .. })\n        | Ok(o @ O::AlreadyApplied { .. }) => o,\n        // The fail-closed refusals — nothing swapped, do NOT restart.\n        Ok(refused) => return report_apply_outcome(refused),\n        Err(e) => {\n            eprintln!(\"UPDATE_APPLY_FAIL:{e}\");\n            return 1;\n        }\n    };\n\n    // 2. Restart the daemon onto the new bytes. Announce the sessions the cycle\n    //    will bounce (they come back on start), stop if up, WAIT for it to be\n    //    actually down (else `start` would see it \"already running\" and skip the\n    //    swap onto new bytes), then start.\n    if spt_daemon::is_running() {\n        let hosted = live_hosted_session_ids();\n        if !hosted.is_empty() {\n            eprintln!(\n                \"Cycling {} hosted session(s) onto the new version (they come back automatically): {}\",\n                hosted.len(),\n                hosted.join(\", \")\n            );\n        }\n        if let Err(e) = spt_daemon::stop_daemon() {\n            eprintln!(\n                \"DAEMON_STOP_FAIL: {e} — the new version is on disk; run `spt daemon start` to finish.\"\n            );\n            return 1;\n        }\n        let down_by = std::time::Instant::now() + std::time::Duration::from_secs(10);\n        while spt_daemon::is_running() && std::time::Instant::now() < down_by {\n            std::thread::sleep(std::time::Duration::from_millis(50));\n        }\n        if spt_daemon::is_running() {\n            eprintln!(\n                \"DAEMON_STOP_TIMEOUT: the daemon did not stop within 10s — the new version is on \\\n                 disk; run `spt daemon start` once it stops to finish.\"\n            );\n            return 1;\n        }\n    }\n    match spt_daemon::start_daemon() {\n        Ok(_) => {\n            let (version, product_version, already) = match applied {\n                O::AppliedDaemonless {\n                    version,\n                    product_version,\n                }\n                | O::AppliedPending {\n…\n                } => (version, product_version, false),\n…\n            };\n…\n        }\n…\n    }\n}"}],"details":{"displayContent":{"text":"fn cmd_update_apply(finish: bool) -> i32 {\n…\n    match outcome {\n…\n        Err(e) => {\n…\n            1\n        }\n    }\n}\n\n/// One leg of the bare `spt update` composite (REQ-UPDATE-DEFAULT-COMPOSITE,\n/// REQ-UPDATE-RESTART-SAFE-SWAP).\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\nenum UpdateLeg {\n    /// `update fetch --apply` — stage + install the core in one step (the\n    /// brain-only cycle: the invoking process survives by construction).\n    FetchApply,\n    /// `update fetch` — stage only; the swap waits for the Finish leg.\n    Fetch,\n    /// `update adapters` — sweep every gh_release adapter.\n    Adapters,\n    /// `update apply --finish` — swap + full daemon restart. LETHAL: from an\n    /// spt-hosted session the invoker's PTY dies here, so this leg is always\n    /// planned LAST (everything else has completed by then).\n    Finish,\n}\n\n/// Plan the composite's leg sequence — pure, unit-gated. The invariants:\n/// core before adapters (core-first doctrine); `--core-only` drops the\n/// adapters leg; `--restart` replaces the in-place apply with stage-now\n/// (`Fetch`) + `Finish` LAST (lethal-leg-last, operator-ruled: the whole-daemon\n/// restart must be the final act so every other leg completes from ANY\n/// invoking context, including a hosted session whose PTY the finish kills).\n// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\nfn plan_update_legs(core_only: bool, restart: bool) -> Vec<UpdateLeg> {\n    let mut legs = Vec::new();\n    legs.push(if restart {\n        UpdateLeg::Fetch\n    } else {\n        UpdateLeg::FetchApply\n    });\n    if !core_only {\n        legs.push(UpdateLeg::Adapters);\n    }\n    if restart {\n        legs.push(UpdateLeg::Finish);\n    }\n    legs\n}\n\n/// Bare `spt update` — the composite front door (REQ-UPDATE-DEFAULT-COMPOSITE):\n/// core first (`fetch --apply`; an already-current core no-ops and the run\n/// becomes adapters-only), then `update adapters`. The invoker survives the\n/// core leg by construction — apply cycles the BRAIN only, the broker and every\n/// PTY keep running (applyhost::apply_staged) — so no re-run machinery exists.\n/// With `--restart` (REQ-UPDATE-RESTART-SAFE-SWAP) the plan is fetch → adapters\n/// → `apply --finish` LAST: the one-step safe full-cycle swap.\n///\n/// Failure policy: a CORE-leg failure (fetch/apply refused, gh missing…)\n/// ABORTS the remaining legs — updating adapters against a channel that just\n/// refused is compounding, and a `--restart` finish after a failed fetch would\n/// bounce the daemon for nothing. An ADAPTERS-leg failure is isolated (that is\n/// its contract): loud, nonzero at exit, but the composite proceeds — under\n/// `--restart` the finish still completes the core cycle. The accepted\n/// asymmetry (new adapters briefly on old-activated core if the finish itself\n/// fails) is loud and operator-attended by nature of the flag.\n// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\nfn cmd_update_composite(core_only: bool, restart: bool) -> i32 {\n    let mut worst = 0;\n    for leg in plan_update_legs(core_only, restart) {\n        let code = match leg {\n            UpdateLeg::FetchApply => cmd_update_fetch(None, None, true),\n            UpdateLeg::Fetch => cmd_update_fetch(None, None, false),\n            UpdateLeg::Adapters => cmd_adapter_update(&spt_store::perch::adapters_dir(), None),\n            UpdateLeg::Finish => cmd_update_apply(true),\n        };\n        if code != 0 {\n            if composite_abort_on_failure(leg) {\n                eprintln!(\n                    \"UPDATE_COMPOSITE_ABORTED: the core update failed — the remaining \\\n                     leg(s) were not run\"\n                );\n                return code;\n            }\n            worst = code;\n        }\n    }\n    worst\n}\n\n/// Whether a failed composite leg aborts the remaining legs — pure, unit-gated.\n/// Only the CORE legs abort (a refused channel poisons the adapters leg, and a\n/// finish after a failed fetch is a pointless daemon bounce); an adapters-leg\n/// failure is isolated by contract and the composite proceeds.\n// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\nfn composite_abort_on_failure(leg: UpdateLeg) -> bool {\n    matches!(leg, UpdateLeg::FetchApply | UpdateLeg::Fetch)\n}\n\n/// Land the staged docs bundle at `$SPT_HOME/docs` — the single current copy,\n/// version-matched to the just-applied release (ADR-0036 §4). Extraction is a\n…\nfn land_staged_docs(cache: &spt_daemon::ReleaseCache) {\n…\n    if let Err(e) = std::fs::rename(&staging, &docs_dir) {\n…\n        let _ = std::fs::remove_dir_all(&staging);\n        eprintln!(\"UPDATE_DOCS_SKIPPED: promote docs: {e} — docs retry next fetch\");\n        return;\n    }\n    let _ = std::fs::remove_dir_all(&old);\n    // Landed — the staged bundle has served its purpose.\n    cache.clear_staged_docs();\n    println!(\"UPDATE_DOCS_LANDED: {}\", docs_dir.display());\n}\n\n/// `spt update apply --finish` — swap the bytes daemonless (never leaving an old\n/// coordinator running new-agent code), then RESTART the daemon onto them so the\n/// whole node runs the new version in one command. The restart rides\n/// REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL: `daemon start` re-runs the previously\n/// online hosted endpoints, so the cycle is not a massacre.\n// [impl->REQ-UPDATE-ONE-SHOT-FINISH]\nfn cmd_update_apply_finish(\n    cache: &spt_daemon::ReleaseCache,\n    keys: &std::path::Path,\n    exe: &std::path::Path,\n) -> i32 {\n    use spt_daemon::ApplyStagedOutcome as O;\n\n    // 1. Swap the bytes with NO broker touched. A daemonless swap can never leave\n    //    a running old broker paired with new brain bytes.\n    let applied = match spt_daemon::apply_staged_daemonless(cache, keys, exe) {\n        Ok(O::NoUpdate) => {\n            println!(\"NO_UPDATE\");\n            return 0;\n        }\n        // The proceed set — bytes are (or already were) on disk; go restart onto them.\n        Ok(o @ O::AppliedDaemonless { .. })\n        | Ok(o @ O::AppliedPending { .. })\n        | Ok(o @ O::AlreadyApplied { .. }) => o,\n        // The fail-closed refusals — nothing swapped, do NOT restart.\n        Ok(refused) => return report_apply_outcome(refused),\n        Err(e) => {\n            eprintln!(\"UPDATE_APPLY_FAIL:{e}\");\n            return 1;\n        }\n    };\n\n    // 2. Restart the daemon onto the new bytes. Announce the sessions the cycle\n    //    will bounce (they come back on start), stop if up, WAIT for it to be\n    //    actually down (else `start` would see it \"already running\" and skip the\n    //    swap onto new bytes), then start.\n    if spt_daemon::is_running() {\n        let hosted = live_hosted_session_ids();\n        if !hosted.is_empty() {\n            eprintln!(\n                \"Cycling {} hosted session(s) onto the new version (they come back automatically): {}\",\n                hosted.len(),\n                hosted.join(\", \")\n            );\n        }\n        if let Err(e) = spt_daemon::stop_daemon() {\n            eprintln!(\n                \"DAEMON_STOP_FAIL: {e} — the new version is on disk; run `spt daemon start` to finish.\"\n            );\n            return 1;\n        }\n        let down_by = std::time::Instant::now() + std::time::Duration::from_secs(10);\n        while spt_daemon::is_running() && std::time::Instant::now() < down_by {\n            std::thread::sleep(std::time::Duration::from_millis(50));\n        }\n        if spt_daemon::is_running() {\n            eprintln!(\n                \"DAEMON_STOP_TIMEOUT: the daemon did not stop within 10s — the new version is on \\\n                 disk; run `spt daemon start` once it stops to finish.\"\n            );\n            return 1;\n        }\n    }\n    match spt_daemon::start_daemon() {\n        Ok(_) => {\n            let (version, product_version, already) = match applied {\n                O::AppliedDaemonless {\n                    version,\n                    product_version,\n                }\n                | O::AppliedPending {\n…\n                } => (version, product_version, false),\n…\n            };\n…\n        }\n…\n    }\n}","startLine":4997,"lineNumbers":[4997,null,5025,null,5036,null,5038,5039,5040,5041,5042,5043,5044,5045,5046,5047,5048,5049,5050,5051,5052,5053,5054,5055,5056,5057,5058,5059,5060,5061,5062,5063,5064,5065,5066,5067,5068,5069,5070,5071,5072,5073,5074,5075,5076,5077,5078,5079,5080,5081,5082,5083,5084,5085,5086,5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102,5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,5113,5114,5115,5116,5117,5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133,5134,5135,null,5142,null,5212,null,5215,5216,5217,5218,5219,5220,5221,5222,5223,5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,5237,5238,5239,5240,5241,5242,5243,5244,5245,5246,5247,5248,5249,5250,5251,5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267,5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283,5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,null,5298,null,5304,null,5310,null,5317,5318]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\cli.rs"}}},"isError":false,"timestamp":1784247434327}}
{"type":"message","id":"59f7daa9","parentId":"3ddfd1ea","timestamp":"2026-07-17T00:17:14.352Z","message":{"role":"toolResult","toolCallId":"call_5t02I449v2sqDhtOjrOHYKl3|fc_099a7567dadef391016a59748a22fc819b8a437ebc07205efe","toolName":"grep","content":[{"type":"text","text":"# crates/\n\n## spt-daemon/tests/\n### pumpdeadline.rs\n 72|        io::ErrorKind::TimedOut,\n*73|        \"the bounded read surfaces TimedOut (the pump's supervised-restart signal)\"\n 74|    );\n 75|    assert!(\n 76|        elapsed >= timeout,\n...\n 96|// 30s carrier deadline (the empirical stall: the update leg burned the full 30s\n*97|// -> TimedOut -> whole-round poison -> supervised restart). The sibling of the\n 98|// carrier-op test above: a carrier `TimedOut` poisons; a peer-reply `TimedOut`\n 99|// drops ordinarily.\n 100|#[test]\n### redispatch.rs\n 3|//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n*4|//! a dispatcher GENERATION whose death drops its workers' broker conns\n 5|//! exactly like a production brain death). NO manual re-serve anywhere: the\n*6|//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n 7|//! hand-stepped a known stream and bypassed exactly the rediscovery/\n 8|//! classification seam that was broken in the field.\n 9|//!\n 10|//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\n*11|//!   endpoint/origin; restart the dispatcher generation only → the\n 12|//!   historical stream neither takes nor clears the current controller\n*13|//!   (post-restart input still renders; no EOF; no replay-storm log line;\n 14|//!   the retired row left the broker's enumeration).\n 15|//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n*16|//!   restart the dispatcher → durable classification (the pinned opener)\n 17|//!   resumes the SAME operator stream.\n 18|//! - D3: ONE injected transient worker-start failure → the claim requeues\n 19|//!   and the retry serves the stream (no permanent abandonment, no\n...\n 108|\n*109|/// One REAL dispatcher generation: the `dispatch_fixture` child running\n 110|/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n 111|/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n*112|struct Generation {\n 113|    child: Child,\n 114|    log: std::path::PathBuf,\n 115|}\n 116|\n*117|impl Generation {\n*118|    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n 119|        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n 120|        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 121|        cmd.arg(broker_name)\n...\n 128|        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n*129|        Generation { child, log }\n 130|    }\n 131|\n 132|    fn stderr_log(&self) -> String {\n...\n 154|/// Render the operator's viewport (dedup `Output` by seq — replays after a\n*155|/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n 156|/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n 157|/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n 158|/// Returns `true` if an EOF for the stream arrived while rendering (the\n...\n 213|// through PRODUCTION rediscovery: a finished historical Attach and a current\n*214|// active Attach on the same endpoint/origin; only the dispatcher generation\n*215|// restarts. The historical stream must neither take nor clear the current\n*216|// controller: post-restart input still renders (the frozen-PTY symptom is\n 217|// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n*218|// clear), the fresh generation logs NO replay of the historical stream id,\n 219|// and the retired row is GONE from the broker's enumeration\n 220|// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221|#[test]\n...\n 234|\n*235|    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n 236|\n 237|    let mut operator = connect_retry(&name_b);\n 238|    let conn = operator\n...\n 269|\n*270|    // ── The brain cycle: kill generation 1, start generation 2 — PRODUCTION\n 271|    // rediscovery re-serves what the broker still enumerates.\n 272|    gen1.kill();\n*273|    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\n 274|\n*275|    // THE regression: input typed AFTER the restart still renders — the\n 276|    // reconstruction re-took the controller (successor re-take parity), and\n 277|    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\n 278|    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\n...\n 281|        !eof_post,\n*282|        \"the current controller's stream saw an EOF across the restart — a replayed \\\n 283|         historical detach cleared it (the 7.41 steal)\"\n 284|    );\n 285|    let painted = screen_of(&view2);\n### reseed.rs\n 7|//! re-seeded over the member control stream and converges to the current\n*8|//! generation — while a **revoked** node, even one that kept its prior-epoch\n 9|//! seed, is denied and never re-admitted (it must re-pair). Same two-host shape\n 10|//! as `seedproofx.rs`/`rosterprop.rs`, plus the rotation skew + the grace seam.\n 11|\n...\n 77|\n*78|/// A current-generation credential carrying the immediately-prior one.\n 79|fn cred_with_prev(name: &str, cur: u8, epoch: u64, prev: u8) -> SubnetCred {\n 80|    SubnetCred {\n 81|        name: name.to_string(),\n...\n 87|\n*88|/// A bare current-generation credential (no prior — a node that has not rotated).\n 89|fn cred(name: &str, s: u8, epoch: u64) -> SubnetCred {\n 90|    SubnetCred {\n 91|        name: name.to_string(),\n...\n 105|fn benign_offliner_is_reseeded_across_a_rotation() {\n*106|    // Fresh node A: rotated to epoch 2 (seed v2), prior generation = v1 @ 1.\n 107|    let a = host(Identity::generate(), vec![cred_with_prev(\"home\", 2, 2, 1)]);\n 108|    // Stale node B: still on epoch 1 (seed v1) — it was offline for the rotation.\n 109|    let b_id = Identity::generate();\n### two_origin_spanning.rs\n 189|\n*190|// [int->REQ-DIGEST-GENERATION-SUPERSEDE] the flynn digest gen-union rig, end to\n 191|// end through the real extractor: a checkpoint/resume REPLAYS the prior\n*192|// generation's transcript into the new session file, so gen B's extracted lines\n 193|// are a superset replay of gen A (identical role/ts/text at the same localseq)\n 194|// plus B's own new tail. RED-first: WITHOUT the supersede pass every A row surfaces\n 195|// TWICE (once per ordinal) — pre-fix `inputs` would be [a-one, a-two, a-one, a-two,\n*196|// b-tail]. Post-fix: each logical row appears ONCE under B's generation, B's tail\n 197|// intact, and the now-fully-superseded ancestor leaves no orphaned /clear divider.\n 198|#[test]\n*199|fn digest_span_supersedes_a_replayed_generation() {\n 200|    let home = tempfile::tempdir().expect(\"tempdir\");\n 201|    std::env::set_var(\"SPT_HOME\", home.path());\n 202|    let hp = home.path();\n...\n 266|    // (a-one, a-two, [clear], a-one, a-two, b-tail) and all survive the window —\n*267|    // RED. With supersede the ancestor's two rows collapse into B's generation.\n 268|    let over = DigestOverride { window_turns: Some(10), ..Default::default() };\n 269|    let digest = project_endpoint_digest(\"ccgen\", &over);\n 270|    let inputs: Vec<_> = digest.turns.iter().filter_map(|t| t.input.as_deref()).collect();\n### wanmsg.rs\n 10|//!   — and survives real QUIC chunking (a record split across two sends);\n*11|//! - **idempotency** (REQ-HAZARD-RESTART-IDEMPOTENT, wire-fed): a replayed\n 12|//!   record (same wire `op_id`) is a no-op — including across a receiver brain\n*13|//!   restart that resubscribes from a stale cursor and re-presents the whole\n 14|//!   feed;\n 15|//! - **origin is transport truth** (REQ-HAZARD-WAN-ORIGIN-AUTH): the access\n 16|//!   gate's subject is the stream table's `remote_id_hex` (the QUIC handshake\n...\n 289|\n*290|// [unit->REQ-HAZARD-RESTART-IDEMPOTENT] receiver brain restart: life 2\n 291|// resubscribes from a STALE cursor (0) and re-presents the whole feed — the\n 292|// durable op-id claims dedup everything life 1 already delivered, so the spool\n 293|// holds each message exactly once.\n 294|#[test]\n*295|fn receiver_restart_replays_feed_without_double_delivery() {\n 296|    init_home();\n 297|    let dir = tempfile::tempdir().expect(\"tempdir\");\n 298|    let (name_a, name_b) = (unique_name(), unique_name());\n### xfer.rs\n 2|//! target node and push one onto it over the broker-owned wire, chunked,\n*3|//! progress-queryable, and exactly-once across a target-brain restart.\n 4|//!\n 5|//! Two in-process brokers (hermetic loopback endpoints, relays disabled) play\n 6|//! the two daemons. The invariants:\n...\n 12|//!   root — a traversal attempt is refused with an `Err` record, not served;\n*13|//! - **restart survival** (REQ-HAZARD-RESTART-IDEMPOTENT): the target brain\n 14|//!   dies mid-push with a partial temp file on disk; the successor re-serves\n 15|//!   the SAME stream from seq 0 — the replayed chunks rewrite in place\n 16|//!   (positional idempotency), the commit renames atomically, and the file is\n...\n 230|// blocked push observes the durable commit echo.\n*231|// [unit->REQ-HAZARD-RESTART-IDEMPOTENT]\n 232|#[test]\n*233|fn push_survives_target_brain_restart_exactly_once() {\n 234|    let dir = tempfile::tempdir().expect(\"tempdir\");\n 235|    let (name_a, name_b) = (unique_name(), unique_name());\n 236|    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n...\n 338|        bytes,\n*339|        \"file byte-identical across the restart\"\n 340|    );\n 341|    assert!(!temp.exists(), \"the commit consumed the temp\");\n 342|\n\n## spt/tests/\n### brain_split.rs\n 38|/// Read the `pid` out of a `brain.ready` body. As of restoration D6-1b the\n*39|/// breadcrumb is self-describing JSON `{pid, generation}` (was bare-pid text);\n 40|/// this is the only reader, migrated in the same commit.\n 41|fn ready_pid(body: &str) -> Option<u32> {\n 42|    serde_json::from_str::<serde_json::Value>(body)\n...\n 95|/// D3-1 (ADR-0018 Q2) — the seed-control lock + liveness is the broker's, so it\n*96|/// is the **always-up anchor**: a brain restart must never perturb it. The\n 97|/// single-daemon-per-`SPT_HOME` ping (`is_running`, what `ensure_running` /\n 98|/// `daemon stop` target) lives in the broker's foreground loop\n 99|/// (`daemon.rs::Daemon::run`), NOT the brain — so killing the brain and watching\n...\n 128|    // Kill the brain; while the supervisor respawns it, the seed anchor (in the\n*129|    // never-restarting broker) must stay continuously up. Sample across the whole\n 130|    // respawn gap — a single not-running read is a failure (the anchor flapped).\n 131|    kill_pid(pid1);\n 132|    let deadline = Instant::now() + Duration::from_secs(30);\n### brain_survive.rs\n 2|//! swap**: a hosted PTY child + a live QUIC connection survive a brain-**PROCESS**\n*3|//! restart that lands on a **swapped on-disk binary**, with the broker holding\n 4|//! both endpoints untouched throughout. This is the `int` E2E that re-points the\n 5|//! regression-masked REQ-DAEMON-2 / REQ-UPD-3 evidence off the in-process\n 6|//! `brain_swap.rs` shape onto a real process boundary.\n...\n 18|//!   a live QUIC conn). The broker is modeled **in-process** — faithful, not a\n*19|//!   weakening: the broker is the never-restarting anchor *by design*, so an\n 20|//!   in-process [`Broker`] runs the **same Broker code holding the same\n 21|//!   resources** the real broker would across a brain swap. The claim under test\n 22|//!   is the brain process + the swapped binary + broker-held endpoint survival —\n...\n 68|use spt_daemon::brain::Brain;\n*69|use spt_daemon::brainproc::{supervise_brain, BrainRestart, NoTrialEnv, StartReason};\n 70|use spt_daemon::codec::write_frame;\n 71|use spt_daemon::endpoint::broker_socket_name;\n 72|use spt_daemon::frame::{Envelope, Role};\n...\n 145|    let pid = v.get(\"pid\")?.as_u64()? as u32;\n*146|    let generation = v.get(\"generation\").and_then(|g| g.as_u64()).unwrap_or(0);\n 147|    let exe_hash = v\n 148|        .get(\"exe_hash\")\n 149|        .and_then(|h| h.as_str())\n 150|        .map(str::to_string);\n*151|    Some((pid, generation, exe_hash))\n 152|}\n 153|\n 154|/// Poll `brain.ready` until it holds a pid (optionally one DIFFERENT from `was`),\n*155|/// returning `(pid, generation, exe_hash)`. `None` if it never appears in time.\n 156|fn wait_ready(\n 157|    path: &std::path::Path,\n 158|    was: Option<u32>,\n...\n 189|#[test]\n*190|fn pty_and_quic_survive_brain_process_restart_onto_swapped_binary() {\n 191|    // The in-process broker + brain children both resolve `broker_socket_name()`\n 192|    // from SPT_HOME, so the supervised child connects to OUR broker. (The\n 193|    // established daemon-test pattern — daemon_e2e.rs et al. set it the same way.)\n...\n 254|    //    pre-D6 respawn loop (no readiness gate); the swap is driven by the\n*255|    //    planned-restart signal, exactly as a real `apply` drives it (D3-3). ──\n 256|    let ready_path = home.path().join(\"brain.ready\");\n 257|    let stop = Arc::new(AtomicBool::new(false));\n*258|    let restart = Arc::new(BrainRestart::new());\n 259|    let selected = Arc::new(Mutex::new(fixture_a.clone()));\n 260|    let home_path = home.path().to_path_buf();\n 261|\n...\n 263|        let stop = Arc::clone(&stop);\n*264|        let restart = Arc::clone(&restart);\n 265|        let selected = Arc::clone(&selected);\n 266|        thread::spawn(move || {\n 267|            supervise_brain(\n 268|                &stop,\n*269|                &restart,\n 270|                Duration::from_millis(50),\n 271|                &NoTrialEnv,\n 272|                Duration::from_secs(30),\n...\n 279|                            \"brain\",\n*280|                            \"--generation\",\n 281|                            &gen.to_string(),\n 282|                            \"--start-reason\",\n 283|                            reason.as_arg(),\n...\n 291|\n*292|    // ── Generation 0: the real child came up from fixture A, resumed the hosted\n 293|    //    session (the first N≥1 cursor-of-record resume), and stamped exe_hash A. ──\n 294|    let (pid0, _gen0, hash0) = wait_ready(&ready_path, None, Duration::from_secs(45))\n 295|        .unwrap_or_else(|| teardown_panic(&stop, \"gen-0 brain never signalled ready\"));\n...\n 308|\n*309|    // ── SWAP: flip the selected binary to B, then trigger a planned restart\n 310|    //    (what `apply` does: swap on disk, signal the brain to cycle). ──\n 311|    *selected.lock().unwrap() = fixture_b.clone();\n*312|    restart.request();\n 313|\n*314|    // ── Generation 1: the supervisor respawned the brain PROCESS onto the\n 315|    //    SWAPPED binary. New pid, exe_hash == B != A (NEW BYTES RAN). ──\n 316|    let (pid1, _gen1, hash1) = wait_ready(&ready_path, Some(pid0), Duration::from_secs(45))\n 317|        .unwrap_or_else(|| teardown_panic(&stop, \"gen-1 brain never respawned onto the swap\"));\n### daemon_refresh_e2e.rs\n*1|//! THE-FORKENING W4 — `spt daemon refresh` on a LIVE daemon with a hosted\n*2|//! endpoint (REQ-DAEMON-REFRESH): the brain generation cycles (new brain pid,\n*3|//! generation bumped) with NO binary swap, while the broker process, the hosted\n 4|//! harness session, and the endpoint's ONLINE perch all survive untouched — the\n 5|//! apply_staged brain-cycle path minus the swap, driven through the REAL CLI\n 6|//! verb. The recovery story: wedged brain-held state no longer needs a full\n...\n 13|//\n*14|// [int->REQ-DAEMON-REFRESH]\n 15|\n 16|use std::path::{Path, PathBuf};\n 17|use std::process::{Child, Command, Output, Stdio};\n...\n 46|\n*47|/// `(pid, generation)` out of `brain.ready`; `None` until it parses.\n 48|fn read_ready(path: &Path) -> Option<(u32, u64)> {\n 49|    let v: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).ok()?).ok()?;\n 50|    Some((\n 51|        v.get(\"pid\")?.as_u64()? as u32,\n*52|        v.get(\"generation\")?.as_u64()?,\n 53|    ))\n 54|}\n 55|\n...\n 59|    while Instant::now() < deadline {\n*60|        if let Some((pid, generation)) = read_ready(path) {\n 61|            if was != Some(pid) {\n*62|                return Some((pid, generation));\n 63|            }\n 64|        }\n 65|        std::thread::sleep(Duration::from_millis(50));\n...\n 75|#[test]\n*76|fn daemon_refresh_cycles_brain_while_hosted_endpoint_survives() {\n 77|    let now_ms = || {\n 78|        std::time::SystemTime::now()\n 79|            .duration_since(std::time::UNIX_EPOCH)\n...\n 182|\n*183|    // ── (5) THE VERB: `spt daemon refresh` — the production CLI path. ──\n*184|    let refresh = {\n 185|        let mut cmd = Command::new(&spt_bin);\n 186|        cmd.no_window()\n*187|            .args([\"daemon\", \"refresh\"])\n 188|            .env(\"SPT_HOME\", home.path());\n 189|        output_bounded(cmd, Duration::from_secs(30))\n 190|    };\n*191|    let refresh_stdout = String::from_utf8_lossy(&refresh.stdout).to_string();\n*192|    let refresh_stderr = String::from_utf8_lossy(&refresh.stderr).to_string();\n 193|\n 194|    // ── (6) The brain must CYCLE: a new pid stamps brain.ready with a bumped\n*195|    //    generation (no binary swapped anywhere). ──\n 196|    let cycled = wait_ready_not(&ready_path, Some(brain_pid_before), Duration::from_secs(30));\n 197|\n*198|    // The broker process must be the SAME OS process (never restarted).\n 199|    let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);\n 200|\n 201|    // The hosted endpoint survives: harness alive + perch back/still ONLINE.\n...\n 216|    eprintln!(\n*217|        \"=== W4 daemon refresh: run_ok={} online={online} harness_pid={harness_pid:?} \\\n*218|         refresh_ok={} brain_before={brain_pid_before}/g{gen_before} cycled={cycled:?} \\\n 219|         broker_alive={broker_still_running} harness_alive_after={harness_alive_after} \\\n*220|         still_online={still_online} ===\\n--- refresh stdout ---\\n{refresh_stdout}\\n\\\n*221|         --- refresh stderr ---\\n{refresh_stderr}\\n=== endpoint run stderr ===\\n{run_stderr}\\n\\\n 222|         === brain stderr ===\\n{brain_stderr}\",\n 223|        run.status.success(),\n### drive_e2e.rs\n 221|    // and poll — EMPTY. The crash-proof token-stamp leg (a frame stamped under\n*222|    // the dead generation is inert even if the clear never ran) is the drivehub\n*223|    // unit `dead_generation_frame_is_never_served`; here we prove the live break.\n 224|    assert!(drive(\"stick\", \"x=0.7\").status.success()); // a pending frame under token_a\n 225|    let shell = spt_runtime::registry::resolve_option(&perch::adapters_dir(), \"mock-shell\")\n 226|        .unwrap()\n### endpoint_autostart_e2e.rs\n*1|//! F-038 W5 — REQ-ENDPOINT-AUTOSTART (restart-brings-it-up acceptance).\n 2|//!\n 3|//! The gap this locks down (autostart.rs header, deployah field-confirm\n 4|//! 2026-07-10): a gateway-class endpoint is infra — the phone treats `mobile-gw`\n...\n 8|//! runs on a boot background thread (`replay_startup_endpoints`, after broker\n*9|//! bind) so a restart brings the endpoint back up on its own — a FRESH session\n 10|//! (shape (a): a startup default, not a session restore).\n 11|//!\n 12|//! This gate proves the FIX end to end by driving the REAL `spt` binary across a\n*13|//! full daemon RESTART, all inside a fully SPT_HOME-isolated home (its own\n 14|//! daemon, never the operator's live one — the idle_edge_drain_e2e isolation\n 15|//! precedent):\n 16|//!   1. a REAL daemon (A) hosts a REAL spt-hosted `cc` endpoint;\n...\n 59|/// anywhere in the body still tears the isolated daemon/harness/psyche tree down\n*60|/// (the restart flow has many more panic points than a single-daemon gate).\n 61|struct Reaper {\n 62|    pids: Mutex<Vec<u32>>,\n 63|}\n...\n 123|/// superseded by the in-process redirect). Reads the whole sink so the token is\n*124|/// found wherever the current generation wrote it.\n 125|fn read_logs_sink(home: &Path) -> String {\n 126|    let dir = home.join(\"logs\");\n 127|    let mut out = String::new();\n...\n 149|#[test]\n*150|fn saved_endpoint_replays_on_daemon_restart() {\n 151|    let reaper = Reaper::new();\n 152|\n 153|    // ── (1) Isolated, NET-LESS SPT_HOME. ──\n...\n 259|    // the fresh replay must supersede), then reap the run-A harness + psyche so\n*260|    // the restart brings a genuinely NEW session up, not a survivor.\n 261|    let rec_a = spt_store::info::read_info(&self_perch);\n 262|    let session_a = rec_a.as_ref().map(|r| r.session_id.clone());\n 263|    if let Some(pid) = spt_store::info::read_pid(&self_perch) {\n...\n 287|\n*288|    // ── (6) Daemon B — the RESTART. NO `endpoint run`. Its boot replay thread\n 289|    //    (after broker bind) must bring `gwauto` back up on its own. ──\n 290|    let daemon_b_log = home.path().join(\"daemon-b.stderr.log\");\n 291|    let log_b = std::fs::File::create(&daemon_b_log).expect(\"create daemon B log\");\n...\n 322|        // Done once the loud token is present AND the perch carries a fresh\n*323|        // (post-restart) session id distinct from run A's.\n 324|        let fresh = matches!((&session_a, &session_b), (Some(a), Some(b)) if !b.is_empty() && a != b);\n 325|        if autostart_line.is_some() && fresh {\n 326|            break;\n...\n 342|    eprintln!(\n*343|        \"=== F-038 W5 AUTOSTART-ON-RESTART GATE: online_a={online_a} run_ok={} \\\n 344|         saved_token={} session_a={session_a:?} session_b={session_b:?} \\\n 345|         autostart_line={autostart_line:?} ===\\n=== endpoint run stderr ===\\n{run_stderr}\\n\\\n 346|         === daemon.json after --save ===\\n{cfg_after_save}\\n\\\n...\n 373|        online_a,\n*374|        \"PRECONDITION: the cc endpoint must bind ONLINE under daemon A before restart\"\n 375|    );\n 376|    // (a) The save fired loud AND landed in daemon.json.\n 377|    assert!(\n...\n 387|        brain_b.is_some(),\n*388|        \"PRECONDITION: daemon B (the restart) must come up with a fresh brain.\\n\\\n 389|         === daemon B stderr ===\\n{daemon_b_stderr}\"\n 390|    );\n*391|    // (b) THE KEYSTONE: the restart's boot replay brought the saved endpoint back\n 392|    //     up on its own — the loud ENDPOINT_AUTOSTART:<id> token is present in\n 393|    //     daemon B's stderr, emitted only on a successful launch_harness_brokered_in.\n 394|    let line = match autostart_line {\n...\n 406|    // (c) A replay is a FRESH session (shape (a): a startup default, not a\n*407|    //     restore) — the post-restart perch must carry a session id distinct from\n 408|    //     run A's.\n 409|    if let (Some(a), Some(b)) = (&session_a, &session_b) {\n 410|        assert!(\n### listen_seed_retry_e2e.rs\n 10|//! 2. **Session-id fallback** (REQ-LISTEN-SESSION-ID-FALLBACK): a session whose\n*11|//!    ephemeral seed is GONE (late go-live / post-daemon-restart) binds via\n 12|//!    `api listen <id> --session-id <sid>` — the loud SID_BIND marker path.\n 13|//!\n 14|//! Both drive the real binary with an explicit `--adapter mock` (bin-only crate:\n...\n 166|// [int->REQ-LISTEN-SESSION-ID-FALLBACK] F-034 leg c: a session with NO live seed\n*167|// (its ephemeral SessionStart seed expired / a daemon restart dropped it) binds via\n 168|// `api listen <id> --session-id <sid>`. The seed path yields NO_SEED, the fallback\n 169|// binds from the sid (loud SID_BIND marker), and the perch carries the sid. This is\n*170|// the post-daemon-restart shape — the seed is simply absent.\n 171|#[test]\n 172|fn no_seed_session_binds_via_session_id_fallback() {\n 173|    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());\n### live_adapt_translation_swap_e2e.rs\n 6|//! `Brain::adapter_apply` → stop the resident translation child (frees the OS lock)\n*7|//! → CRC-swap the install dir → restart the translation from the new manifest path\n 8|//! — and the previously-LOCKED install-dir binary is overwritten with the NEW bytes\n*9|//! WHILE the endpoint/session keeps running untouched (brain-parity, no restart).\n 10|//!\n 11|//! Real `spt daemon run` broker + supervised brain + a REAL dummy-harness\n 12|//! `[session.self]` session + a REAL translation binary (`translate_proof_fixture`,\n...\n 108|#[test]\n*109|fn adapter_apply_swaps_locked_translation_binary_without_restarting_endpoint() {\n 110|    // Serialize against the sibling tests — shared process-global SPT_HOME.\n 111|    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());\n 112|    let now_ms = || {\n...\n 240|    //    This proves the binary IS locked — the exact condition the daemon-coordinated\n*241|    //    apply must work around (stop child → swap → restart). On Unix this won't lock,\n 242|    //    so we skip the pre-assert and just proceed to the apply. ──\n 243|    #[cfg(windows)]\n 244|    let direct_overwrite_locked = {\n...\n 286|    //    broker stops the resident translation child (frees the lock), CRC-swaps the\n*287|    //    install dir from staging, restarts the translation from the new path. ──\n 288|    let mut brain = Brain::cold_start(&broker_socket_name(), now_ms())\n 289|        .expect(\"connect a Brain to the live broker for adapter_apply\");\n 290|    let apply_result = brain.adapter_apply(&AdapterApplyReq {\n...\n 299|    //        LANDED on the previously-locked binary);\n*300|    //    (b) the session/endpoint is STILL alive (no restart — brain-parity): the\n 301|    //        harness pid is alive AND the perch is still ONLINE. ──\n 302|    let hash_after = file_hash(&xlate_install);\n 303|    let swap_landed = hash_after.is_some()\n...\n 377|        \"REQ-ADAPTER-LIVE-UPDATE: Brain::adapter_apply must return Ok (the broker stopped \\\n*378|         the resident translation child, CRC-swapped the install dir, and restarted the \\\n 379|         translation) — got {:?}.\\n=== brain stderr ===\\n{brain_stderr}\",\n 380|        apply_result.err().map(|e| e.to_string())\n 381|    );\n...\n 392|\n*393|    // Brain-parity: the endpoint NEVER restarted across the live update.\n 394|    assert!(\n 395|        harness_alive_after,\n 396|        \"REQ-ADAPTER-LIVE-UPDATE (brain-parity): the harness session must STILL be ALIVE \\\n 397|         after the live update — only the translation child cycles, the endpoint never \\\n*398|         restarts. harness_pid={harness_pid:?} was dead post-apply.\\n\\\n 399|         === brain stderr ===\\n{brain_stderr}\"\n 400|    );\n 401|    assert!(\n...\n 792|    );\n*793|    // Brain-parity: the composite endpoint never restarted across the live update.\n 794|    assert!(\n 795|        harness_alive_after,\n 796|        \"REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP (brain-parity): the cc:prof harness must STILL be \\\n### n1_compat.rs\n 6|//! broker… the single most update-frequency-sensitive invariant\"). D3-2 added\n*7|//! the first versioned spawn-time field — `--generation` / `--start-reason` —\n 8|//! so this is the first field with an N-1 window to assert.\n 9|//!\n 10|//! **What this scaffold proves now (single binary, both roles):** the current\n...\n 14|//!     shape an older broker that predates D3-2 would spawn → the brain must\n*15|//!     default (generation 0 / `cold`), never die on an arity/unknown-arg error\n 16|//!     before it can rehydrate (KH-2.3's named failure mode); and\n*17|//!   - the **stamped** new-broker invocation (`daemon brain --generation N\n 18|//!     --start-reason R`).\n 19|//!\n 20|//! A clap parse failure exits IMMEDIATELY with usage code 2; a successful parse\n...\n 83|    // 1. BARE old-broker invocation — the pre-D3-2 spawn shape. Must parse\n*84|    //    (default generation 0 / cold) and reach the connect loop, never exit 2.\n 85|    {\n 86|        let home = tempfile::tempdir().unwrap();\n 87|        let mut brain = spawn_brain(&spt_bin, home.path(), &[]);\n...\n 104|            home.path(),\n*105|            &[\"--generation\", \"9\", \"--start-reason\", \"update\"],\n 106|        );\n 107|        let alive = still_running_after(&mut brain, grace);\n 108|        let _ = brain.kill();\n...\n 111|            alive,\n*112|            \"stamped `daemon brain --generation N --start-reason R` must parse + reach the \\\n 113|             connect loop\"\n 114|        );\n 115|    }\n### n1_pairing.rs\n 19|//! window: its broker spawns its own brain with the *bare* pre-D3-2 argv (no\n*20|//! `--generation` / `--start-reason`), and the current brain accepts both shapes.\n 21|//! **Advance rule:** the pin advances to the latest PUBLISHED split-era release\n 22|//! once one exists — so post-v0.4.0 this gate automatically tests the real fleet\n 23|//! N-1, not a frozen D1 ref.\n...\n 126|            \"brain\",\n*127|            \"--generation\",\n 128|            \"99\",\n 129|            \"--start-reason\",\n 130|            \"update\",\n### projindex_writer_e2e.rs\n 7|//! daemon start (index published from nothing, batched counters on the status\n*8|//! surface) → daemon restart (warm start: unchanged generation performs NO\n 9|//! scan, the persisted snapshot is byte-untouched) → the five invalidation\n 10|//! event classes fired through their PRODUCTION funnels (the store's own\n 11|//! nudges for context/rename/fork/purge via the real CLI verbs / ContextStore\n...\n 52|\n*53|/// `(pid, generation)` out of `brain.ready`; `None` until it parses.\n 54|fn read_ready(path: &Path) -> Option<(u32, u64)> {\n 55|    let v: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).ok()?).ok()?;\n*56|    Some((v.get(\"pid\")?.as_u64()? as u32, v.get(\"generation\")?.as_u64()?))\n 57|}\n 58|\n 59|/// Poll `brain.ready` until it holds a pid different from `was`, up to `budget`.\n...\n 62|    while Instant::now() < deadline {\n*63|        if let Some((pid, generation)) = read_ready(path) {\n 64|            if was != Some(pid) {\n*65|                return Some((pid, generation));\n 66|            }\n 67|        }\n 68|        std::thread::sleep(Duration::from_millis(50));\n...\n 186|    );\n*187|    assert!(!cold.source_generation.is_empty(), \"fingerprint stamped\");\n 188|\n 189|    // The observability surface rides `daemon status --json` (index presence\n 190|    // alone is not health), and the batched counters are on it.\n...\n 202|        .expect(\"status carries the project_index observability block\");\n*203|    assert_eq!(pi[\"source_generation\"], cold.source_generation.as_str());\n 204|    assert_eq!(pi[\"endpoints\"], 3);\n 205|    assert_eq!(\n 206|        pi[\"last_cycle\"][\"branch_enumerations\"], 1,\n...\n 209|\n*210|    // ── (3) WARM START: restart the daemon over the unchanged store. ──\n 211|    let _ = {\n 212|        let mut cmd = Command::new(&spt_bin);\n 213|        cmd.no_window()\n...\n 242|    // The persisted index is immediately readable (it was never removed), and\n*243|    // the boot reconcile over the unchanged generation performs NO scan and NO\n 244|    // derivation — and does not rewrite the snapshot.\n 245|    // [int->REQ-PROJECT-INDEX-WRITER]\n 246|    let warm_deadline = Instant::now() + Duration::from_secs(20);\n...\n 252|            if v[\"last_run_ms\"].as_u64().unwrap_or(0) > run_before\n*253|                && !v[\"pending_refresh\"].as_bool().unwrap_or(true)\n 254|            {\n 255|                break v;\n 256|            }\n...\n 267|        warm_stats[\"last_cycle\"][\"tree_scans\"], 0,\n*268|        \"warm start with an unchanged generation performs NO scan\"\n 269|    );\n 270|    assert_eq!(warm_stats[\"last_cycle\"][\"derivations\"], 0);\n 271|    assert_eq!(\n...\n 273|        bytes_before,\n*274|        \"the unchanged snapshot is byte-untouched across the restart\"\n 275|    );\n 276|\n 277|    // ── (4) EVENT INVALIDATIONS against the LIVE daemon. ──\n...\n 307|    cs.commit_project(\"beta\", \"ep2 slice\").unwrap().unwrap();\n*308|    wait_index(&index_path, Duration::from_secs(15), \"context commit → new generation\", |i| {\n*309|        i.source_generation != cold.source_generation\n 310|            // ep2's RENDERED attribution stays origin-cwd (higher precedence);\n*311|            // the membership refresh is visible through the generation move.\n 312|            && i.endpoints.get(\"ep2\").is_some_and(|e| e.source.as_deref() == Some(\"origin-cwd\"))\n 313|    });\n 314|\n### resume_no_control_steal_e2e.rs\n 97|\n*98|use spt_daemon::brainproc::{ready_generation_at, supervise_brain, BrainRestart, StartReason, TrialEnv};\n 99|use spt_daemon::codec::{read_frame, write_frame};\n 100|use spt_daemon::endpoint::broker_socket_name;\n 101|use spt_daemon::frame::{Envelope, Role};\n...\n 302|    }\n*303|    fn ready_generation(&self) -> Option<u64> {\n*304|        ready_generation_at(&self.ready_path)\n 305|    }\n 306|    fn clear_ready(&self) {\n 307|        let _ = std::fs::remove_file(&self.ready_path);\n...\n 399|        thread::spawn(move || {\n*400|            let restart = BrainRestart::new();\n 401|            supervise_brain(\n 402|                &stop_sup,\n*403|                &restart,\n 404|                Duration::from_millis(50),\n 405|                env_sup.as_ref(),\n 406|                Duration::from_secs(60),\n...\n 419|                            \"brain\",\n*420|                            \"--generation\",\n 421|                            &gen.to_string(),\n 422|                            \"--start-reason\",\n 423|                            reason.as_arg(),\n### trial_drain_drive_e2e.rs\n 3|//! production `run_brain` self-drives the broker's controller-liveness reap so a\n*4|//! hard-killed prior generation's black-holed LOCAL controller conn cannot strand\n 5|//! the promotion DRAINED gate. This is the process-level upgrade of the in-process\n 6|//! `crates/spt-daemon/tests/false_promote.rs` (REQ-UPDATE-PROMOTE-DRAINED): there the\n 7|//! candidate is a fake `sleep`/`ping` and the wedge is drained by an EXTERNAL\n...\n 23|//! candidate DRIVES the live older broker's already-shipped reap and un-strands its\n*24|//! OWN trial with no coordinated broker restart.\n 25|//!\n 26|//! ## Why the wedge is established AFTER the candidate boots (the resume paradox)\n 27|//!\n...\n 39|//! hosts no PTY sessions; brainproc.rs:184) and the wedged controller was a\n*40|//! prior-generation conn resume did NOT cover. This rig reproduces that faithfully:\n 41|//! the candidate boots + resumes against an EMPTY session set, THEN conn A spawns a\n 42|//! flood session and black-holes as its by:None controller — a session the boot-once\n 43|//! resume never covered, exactly the field's un-resumed wedge. A black-holed by:None\n...\n 53|//! The real subprocess stamps `brain.ready` autonomously the instant it boots — before\n*54|//! the test can establish the wedge. So the rig's [`TrialEnv::ready_generation`]\n 55|//! WITHHOLDS the candidate's readiness from `run_trial` until the test ARMS it (after\n 56|//! the old-gen wedge is up + matured), then reads the REAL `brain.ready` file. This is\n 57|//! the process-level analog of false_promote's scripted-ready `Mutex`: it guarantees\n...\n 108|use spt_daemon::brain::Brain;\n*109|use spt_daemon::brainproc::{ready_generation_at, supervise_brain, BrainRestart, StartReason, TrialEnv};\n 110|use spt_daemon::codec::{read_frame, write_frame};\n 111|use spt_daemon::endpoint::broker_socket_name;\n 112|use spt_daemon::frame::{Envelope, Role};\n...\n 292|    }\n*293|    fn ready_generation(&self) -> Option<u64> {\n 294|        // LATCH: withhold the real subprocess's readiness from run_trial until the\n 295|        // test has armed the old-gen wedge, so the DRAINED gate has a matured wedge to\n 296|        // block on when readiness is first seen (never a trivial no-wedge promote).\n...\n 299|        }\n*300|        ready_generation_at(&self.ready_path)\n 301|    }\n 302|    fn clear_ready(&self) {\n 303|        let _ = std::fs::remove_file(&self.ready_path);\n...\n 373|        thread::spawn(move || {\n*374|            let restart = BrainRestart::new();\n 375|            supervise_brain(\n 376|                &stop_sup,\n*377|                &restart,\n 378|                Duration::from_millis(50),\n 379|                env_sup.as_ref(),\n 380|                Duration::from_secs(60), // generous window — covers a slow-CI boot (eaten before the\n...\n 394|                            \"brain\",\n*395|                            \"--generation\",\n 396|                            &gen.to_string(),\n 397|                            \"--start-reason\",\n 398|                            reason.as_arg(),\n...\n 417|    //    resume, so resume never covered it. dispatch_spawn pre-attaches A as the\n*418|    //    session's LOCAL controller (by = None) — the old-generation-conn identity.\n 419|    //    A then goes silent: the child's ONE burst fills A's recv buffer, A's broker-side\n 420|    //    controller writer parks BLOCKED in write_frame (`write_blocked_since` set), and\n 421|    //    with no further output the orthogonal 5s output-driven detach never fires — so\n### tunnel_e2e.rs\n 14|//! tunnel (a post-break resolve finds none); (3) no-stale-stream-across-relink (R1)\n*15|//! — bytes pending in the pre-break generation are NEVER surfaced to the relinked\n 16|//! instance's fresh tunnel.\n 17|//\n 18|// [int->REQ-SHELL-4]\n...\n 252|    // relink (R1). Send a pending payload, DON'T drain it, break the link, relink to\n*253|    // a fresh generation, and confirm the relinked tunnel never serves the stale\n 254|    // bytes.\n 255|    shell_send(&token_a, b\"STALE-PRE-BREAK-BYTES\".to_vec()); // pending in the owner end's ring\n 256|    let shell = spt_runtime::registry::resolve_option(&perch::adapters_dir(), \"mock-shell\")\n...\n 280|    // Drain the fresh owner end a few times — the stale \"STALE-PRE-BREAK-BYTES\" from\n*281|    // the dead generation must NEVER appear (no-stale-stream-across-relink, R1).\n 282|    let mut fresh = Vec::new();\n 283|    for _ in 0..30 {\n 284|        let out = spt(&[\"shell\", \"tunnel\", \"Scout\", \"recv\", \"--owner\", \"doyle\"]);\n### twohost_cli.rs\n 31|//!    honest both-sockets death: seed + broker die with the process) → A's\n*32|//!    child paints the reconnect banner → B's daemon restarts and the ticker\n 33|//!    respawns → the viewport reheals on the production loop.\n 34|//!\n 35|//! ## Rendezvous (differs from the ladder — deliberately)\n...\n 245|    fn start(home: &Path, spt: &Path, tag: &str) -> DaemonGuard {\n*246|        // A prior generation's ready-file must never satisfy this bring-up.\n 247|        let ready = home.join(\"brain.ready\");\n 248|        let _ = std::fs::remove_file(&ready);\n 249|        let log = home.join(format!(\"brain-{tag}.stderr.log\"));\n...\n 695|    daemon.stop();\n*696|    println!(\"GATED role B: daemon DOWN (honest stop) — restarting\");\n 697|    let daemon2 = DaemonGuard::start(home.path(), &spt, \"b2\");\n*698|    // Re-anchor the ticker perch on generation 2's pid BEFORE anything\n*699|    // re-advertises: the old anchor died with generation 1, and a dead-pid row\n 700|    // would replicate Offline to A mid-reheal — unroutable for the re-dial.\n 701|    info::mutate_info(&perch_rc, |rec| {\n 702|        rec.pid = info::PidValue::Numeric(daemon2.broker_pid())\n...\n 712|    signal(&rig, &rig.b_hex(), ID_RC, SIG_BYE);\n*713|    // MUTUAL-HOLD (attempt-4 red): the bye rides THIS generation-2 daemon's\n 714|    // outbound pump, and a fresh daemon's dial to A goes through discovery —\n 715|    // seconds to minutes, not a sleep. Hold the daemon serving until the bye\n 716|    // observably arrived: A ACKs by DISMISSING the row, and the dismissed=true"}],"details":{"scopePath":"crates/spt-daemon/tests, crates/spt/tests","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":157,"fileCount":20,"files":["crates/spt-daemon/tests/pumpdeadline.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/reseed.rs","crates/spt-daemon/tests/two_origin_spanning.rs","crates/spt-daemon/tests/wanmsg.rs","crates/spt-daemon/tests/xfer.rs","crates/spt/tests/brain_split.rs","crates/spt/tests/brain_survive.rs","crates/spt/tests/daemon_refresh_e2e.rs","crates/spt/tests/drive_e2e.rs","crates/spt/tests/endpoint_autostart_e2e.rs","crates/spt/tests/listen_seed_retry_e2e.rs","crates/spt/tests/live_adapt_translation_swap_e2e.rs","crates/spt/tests/n1_compat.rs","crates/spt/tests/n1_pairing.rs","crates/spt/tests/projindex_writer_e2e.rs","crates/spt/tests/resume_no_control_steal_e2e.rs","crates/spt/tests/trial_drain_drive_e2e.rs","crates/spt/tests/tunnel_e2e.rs","crates/spt/tests/twohost_cli.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/pumpdeadline.rs","count":2},{"path":"crates/spt-daemon/tests/redispatch.rs","count":20},{"path":"crates/spt-daemon/tests/reseed.rs","count":4},{"path":"crates/spt-daemon/tests/two_origin_spanning.rs","count":5},{"path":"crates/spt-daemon/tests/wanmsg.rs","count":4},{"path":"crates/spt-daemon/tests/xfer.rs","count":5},{"path":"crates/spt/tests/brain_split.rs","count":3},{"path":"crates/spt/tests/brain_survive.rs","count":16},{"path":"crates/spt/tests/daemon_refresh_e2e.rs","count":20},{"path":"crates/spt/tests/drive_e2e.rs","count":2},{"path":"crates/spt/tests/endpoint_autostart_e2e.rs","count":14},{"path":"crates/spt/tests/listen_seed_retry_e2e.rs","count":3},{"path":"crates/spt/tests/live_adapt_translation_swap_e2e.rs","count":10},{"path":"crates/spt/tests/n1_compat.rs","count":6},{"path":"crates/spt/tests/n1_pairing.rs","count":2},{"path":"crates/spt/tests/projindex_writer_e2e.rs","count":15},{"path":"crates/spt/tests/resume_no_control_steal_e2e.rs","count":6},{"path":"crates/spt/tests/trial_drain_drive_e2e.rs","count":11},{"path":"crates/spt/tests/tunnel_e2e.rs","count":3},{"path":"crates/spt/tests/twohost_cli.rs","count":6}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/\n\n## spt-daemon/tests/\n### pumpdeadline.rs\n  72│        io::ErrorKind::TimedOut,\n *73│        \"the bounded read surfaces TimedOut (the pump's supervised-restart signal)\"\n  74│    );\n  75│    assert!(\n  76│        elapsed >= timeout,\n    │...\n  96│// 30s carrier deadline (the empirical stall: the update leg burned the full 30s\n *97│// -> TimedOut -> whole-round poison -> supervised restart). The sibling of the\n  98│// carrier-op test above: a carrier `TimedOut` poisons; a peer-reply `TimedOut`\n  99│// drops ordinarily.\n 100│#[test]\n### redispatch.rs\n   3│//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n  *4│//! a dispatcher GENERATION whose death drops its workers' broker conns\n   5│//! exactly like a production brain death). NO manual re-serve anywhere: the\n  *6│//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n   7│//! hand-stepped a known stream and bypassed exactly the rediscovery/\n   8│//! classification seam that was broken in the field.\n   9│//!\n  10│//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\n *11│//!   endpoint/origin; restart the dispatcher generation only → the\n  12│//!   historical stream neither takes nor clears the current controller\n *13│//!   (post-restart input still renders; no EOF; no replay-storm log line;\n  14│//!   the retired row left the broker's enumeration).\n  15│//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n *16│//!   restart the dispatcher → durable classification (the pinned opener)\n  17│//!   resumes the SAME operator stream.\n  18│//! - D3: ONE injected transient worker-start failure → the claim requeues\n  19│//!   and the retry serves the stream (no permanent abandonment, no\n    │...\n 108│\n*109│/// One REAL dispatcher generation: the `dispatch_fixture` child running\n 110│/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n 111│/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n*112│struct Generation {\n 113│    child: Child,\n 114│    log: std::path::PathBuf,\n 115│}\n 116│\n*117│impl Generation {\n*118│    fn spawn(broker_name: &str, log: std::path::PathBuf, extra_env: &[(&str, &str)]) -> Generation {\n 119│        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n 120│        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 121│        cmd.arg(broker_name)\n    │...\n 128│        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n*129│        Generation { child, log }\n 130│    }\n 131│\n 132│    fn stderr_log(&self) -> String {\n    │...\n 154│/// Render the operator's viewport (dedup `Output` by seq — replays after a\n*155│/// generation restart re-transmit and are dropped at this cursor; a re-serve's\n 156│/// synthesized repaint may jump FORWARD, which is accepted and baselined)\n 157│/// until `needle` appears in the accumulated bytes, or panic at the deadline.\n 158│/// Returns `true` if an EOF for the stream arrived while rendering (the\n    │...\n 213│// through PRODUCTION rediscovery: a finished historical Attach and a current\n*214│// active Attach on the same endpoint/origin; only the dispatcher generation\n*215│// restarts. The historical stream must neither take nor clear the current\n*216│// controller: post-restart input still renders (the frozen-PTY symptom is\n 217│// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n*218│// clear), the fresh generation logs NO replay of the historical stream id,\n 219│// and the retired row is GONE from the broker's enumeration\n 220│// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221│#[test]\n    │...\n 234│\n*235│    let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);\n 236│\n 237│    let mut operator = connect_retry(&name_b);\n 238│    let conn = operator\n    │...\n 269│\n*270│    // ── The brain cycle: kill generation 1, start generation 2 — PRODUCTION\n 271│    // rediscovery re-serves what the broker still enumerates.\n 272│    gen1.kill();\n*273│    let gen2 = Generation::spawn(&name_a, dir.path().join(\"gen2.log\"), &[]);\n 274│\n*275│    // THE regression: input typed AFTER the restart still renders — the\n 276│    // reconstruction re-took the controller (successor re-take parity), and\n 277│    // no historical replay stole/cleared it (pre-fix: frozen PTY here).\n 278│    send_attach_input(&mut operator, s2, b\"CUR-TWO\\r\", 12).expect(\"input s2 post\");\n    │...\n 281│        !eof_post,\n*282│        \"the current controller's stream saw an EOF across the restart — a replayed \\\n 283│         historical detach cleared it (the 7.41 steal)\"\n 284│    );\n 285│    let painted = screen_of(&view2);\n### reseed.rs\n   7│//! re-seeded over the member control stream and converges to the current\n  *8│//! generation — while a **revoked** node, even one that kept its prior-epoch\n   9│//! seed, is denied and never re-admitted (it must re-pair). Same two-host shape\n  10│//! as `seedproofx.rs`/`rosterprop.rs`, plus the rotation skew + the grace seam.\n  11│\n    │...\n  77│\n *78│/// A current-generation credential carrying the immediately-prior one.\n  79│fn cred_with_prev(name: &str, cur: u8, epoch: u64, prev: u8) -> SubnetCred {\n  80│    SubnetCred {\n  81│        name: name.to_string(),\n    │...\n  87│\n *88│/// A bare current-generation credential (no prior — a node that has not rotated).\n  89│fn cred(name: &str, s: u8, epoch: u64) -> SubnetCred {\n  90│    SubnetCred {\n  91│        name: name.to_string(),\n    │...\n 105│fn benign_offliner_is_reseeded_across_a_rotation() {\n*106│    // Fresh node A: rotated to epoch 2 (seed v2), prior generation = v1 @ 1.\n 107│    let a = host(Identity::generate(), vec![cred_with_prev(\"home\", 2, 2, 1)]);\n 108│    // Stale node B: still on epoch 1 (seed v1) — it was offline for the rotation.\n 109│    let b_id = Identity::generate();\n### two_origin_spanning.rs\n 189│\n*190│// [int->REQ-DIGEST-GENERATION-SUPERSEDE] the flynn digest gen-union rig, end to\n 191│// end through the real extractor: a checkpoint/resume REPLAYS the prior\n*192│// generation's transcript into the new session file, so gen B's extracted lines\n 193│// are a superset replay of gen A (identical role/ts/text at the same localseq)\n 194│// plus B's own new tail. RED-first: WITHOUT the supersede pass every A row surfaces\n 195│// TWICE (once per ordinal) — pre-fix `inputs` would be [a-one, a-two, a-one, a-two,\n*196│// b-tail]. Post-fix: each logical row appears ONCE under B's generation, B's tail\n 197│// intact, and the now-fully-superseded ancestor leaves no orphaned /clear divider.\n 198│#[test]\n*199│fn digest_span_supersedes_a_replayed_generation() {\n 200│    let home = tempfile::tempdir().expect(\"tempdir\");\n 201│    std::env::set_var(\"SPT_HOME\", home.path());\n 202│    let hp = home.path();\n    │...\n 266│    // (a-one, a-two, [clear], a-one, a-two, b-tail) and all survive the window —\n*267│    // RED. With supersede the ancestor's two rows collapse into B's generation.\n 268│    let over = DigestOverride { window_turns: Some(10), ..Default::default() };\n 269│    let digest = project_endpoint_digest(\"ccgen\", &over);\n 270│    let inputs: Vec<_> = digest.turns.iter().filter_map(|t| t.input.as_deref()).collect();\n### wanmsg.rs\n  10│//!   — and survives real QUIC chunking (a record split across two sends);\n *11│//! - **idempotency** (REQ-HAZARD-RESTART-IDEMPOTENT, wire-fed): a replayed\n  12│//!   record (same wire `op_id`) is a no-op — including across a receiver brain\n *13│//!   restart that resubscribes from a stale cursor and re-presents the whole\n  14│//!   feed;\n  15│//! - **origin is transport truth** (REQ-HAZARD-WAN-ORIGIN-AUTH): the access\n  16│//!   gate's subject is the stream table's `remote_id_hex` (the QUIC handshake\n    │...\n 289│\n*290│// [unit->REQ-HAZARD-RESTART-IDEMPOTENT] receiver brain restart: life 2\n 291│// resubscribes from a STALE cursor (0) and re-presents the whole feed — the\n 292│// durable op-id claims dedup everything life 1 already delivered, so the spool\n 293│// holds each message exactly once.\n 294│#[test]\n*295│fn receiver_restart_replays_feed_without_double_delivery() {\n 296│    init_home();\n 297│    let dir = tempfile::tempdir().expect(\"tempdir\");\n 298│    let (name_a, name_b) = (unique_name(), unique_name());\n### xfer.rs\n   2│//! target node and push one onto it over the broker-owned wire, chunked,\n  *3│//! progress-queryable, and exactly-once across a target-brain restart.\n   4│//!\n   5│//! Two in-process brokers (hermetic loopback endpoints, relays disabled) play\n   6│//! the two daemons. The invariants:\n    │...\n  12│//!   root — a traversal attempt is refused with an `Err` record, not served;\n *13│//! - **restart survival** (REQ-HAZARD-RESTART-IDEMPOTENT): the target brain\n  14│//!   dies mid-push with a partial temp file on disk; the successor re-serves\n  15│//!   the SAME stream from seq 0 — the replayed chunks rewrite in place\n  16│//!   (positional idempotency), the commit renames atomically, and the file is\n    │...\n 230│// blocked push observes the durable commit echo.\n*231│// [unit->REQ-HAZARD-RESTART-IDEMPOTENT]\n 232│#[test]\n*233│fn push_survives_target_brain_restart_exactly_once() {\n 234│    let dir = tempfile::tempdir().expect(\"tempdir\");\n 235│    let (name_a, name_b) = (unique_name(), unique_name());\n 236│    let _broker_a = net_broker(&name_a, &dir.path().join(\"a\"));\n    │...\n 338│        bytes,\n*339│        \"file byte-identical across the restart\"\n 340│    );\n 341│    assert!(!temp.exists(), \"the commit consumed the temp\");\n 342│\n\n## spt/tests/\n### brain_split.rs\n  38│/// Read the `pid` out of a `brain.ready` body. As of restoration D6-1b the\n *39│/// breadcrumb is self-describing JSON `{pid, generation}` (was bare-pid text);\n  40│/// this is the only reader, migrated in the same commit.\n  41│fn ready_pid(body: &str) -> Option<u32> {\n  42│    serde_json::from_str::<serde_json::Value>(body)\n    │...\n  95│/// D3-1 (ADR-0018 Q2) — the seed-control lock + liveness is the broker's, so it\n *96│/// is the **always-up anchor**: a brain restart must never perturb it. The\n  97│/// single-daemon-per-`SPT_HOME` ping (`is_running`, what `ensure_running` /\n  98│/// `daemon stop` target) lives in the broker's foreground loop\n  99│/// (`daemon.rs::Daemon::run`), NOT the brain — so killing the brain and watching\n    │...\n 128│    // Kill the brain; while the supervisor respawns it, the seed anchor (in the\n*129│    // never-restarting broker) must stay continuously up. Sample across the whole\n 130│    // respawn gap — a single not-running read is a failure (the anchor flapped).\n 131│    kill_pid(pid1);\n 132│    let deadline = Instant::now() + Duration::from_secs(30);\n### brain_survive.rs\n   2│//! swap**: a hosted PTY child + a live QUIC connection survive a brain-**PROCESS**\n  *3│//! restart that lands on a **swapped on-disk binary**, with the broker holding\n   4│//! both endpoints untouched throughout. This is the `int` E2E that re-points the\n   5│//! regression-masked REQ-DAEMON-2 / REQ-UPD-3 evidence off the in-process\n   6│//! `brain_swap.rs` shape onto a real process boundary.\n    │...\n  18│//!   a live QUIC conn). The broker is modeled **in-process** — faithful, not a\n *19│//!   weakening: the broker is the never-restarting anchor *by design*, so an\n  20│//!   in-process [`Broker`] runs the **same Broker code holding the same\n  21│//!   resources** the real broker would across a brain swap. The claim under test\n  22│//!   is the brain process + the swapped binary + broker-held endpoint survival —\n    │...\n  68│use spt_daemon::brain::Brain;\n *69│use spt_daemon::brainproc::{supervise_brain, BrainRestart, NoTrialEnv, StartReason};\n  70│use spt_daemon::codec::write_frame;\n  71│use spt_daemon::endpoint::broker_socket_name;\n  72│use spt_daemon::frame::{Envelope, Role};\n    │...\n 145│    let pid = v.get(\"pid\")?.as_u64()? as u32;\n*146│    let generation = v.get(\"generation\").and_then(|g| g.as_u64()).unwrap_or(0);\n 147│    let exe_hash = v\n 148│        .get(\"exe_hash\")\n 149│        .and_then(|h| h.as_str())\n 150│        .map(str::to_string);\n*151│    Some((pid, generation, exe_hash))\n 152│}\n 153│\n 154│/// Poll `brain.ready` until it holds a pid (optionally one DIFFERENT from `was`),\n*155│/// returning `(pid, generation, exe_hash)`. `None` if it never appears in time.\n 156│fn wait_ready(\n 157│    path: &std::path::Path,\n 158│    was: Option<u32>,\n    │...\n 189│#[test]\n*190│fn pty_and_quic_survive_brain_process_restart_onto_swapped_binary() {\n 191│    // The in-process broker + brain children both resolve `broker_socket_name()`\n 192│    // from SPT_HOME, so the supervised child connects to OUR broker. (The\n 193│    // established daemon-test pattern — daemon_e2e.rs et al. set it the same way.)\n    │...\n 254│    //    pre-D6 respawn loop (no readiness gate); the swap is driven by the\n*255│    //    planned-restart signal, exactly as a real `apply` drives it (D3-3). ──\n 256│    let ready_path = home.path().join(\"brain.ready\");\n 257│    let stop = Arc::new(AtomicBool::new(false));\n*258│    let restart = Arc::new(BrainRestart::new());\n 259│    let selected = Arc::new(Mutex::new(fixture_a.clone()));\n 260│    let home_path = home.path().to_path_buf();\n 261│\n    │...\n 263│        let stop = Arc::clone(&stop);\n*264│        let restart = Arc::clone(&restart);\n 265│        let selected = Arc::clone(&selected);\n 266│        thread::spawn(move || {\n 267│            supervise_brain(\n 268│                &stop,\n*269│                &restart,\n 270│                Duration::from_millis(50),\n 271│                &NoTrialEnv,\n 272│                Duration::from_secs(30),\n    │...\n 279│                            \"brain\",\n*280│                            \"--generation\",\n 281│                            &gen.to_string(),\n 282│                            \"--start-reason\",\n 283│                            reason.as_arg(),\n    │...\n 291│\n*292│    // ── Generation 0: the real child came up from fixture A, resumed the hosted\n 293│    //    session (the first N≥1 cursor-of-record resume), and stamped exe_hash A. ──\n 294│    let (pid0, _gen0, hash0) = wait_ready(&ready_path, None, Duration::from_secs(45))\n 295│        .unwrap_or_else(|| teardown_panic(&stop, \"gen-0 brain never signalled ready\"));\n    │...\n 308│\n*309│    // ── SWAP: flip the selected binary to B, then trigger a planned restart\n 310│    //    (what `apply` does: swap on disk, signal the brain to cycle). ──\n 311│    *selected.lock().unwrap() = fixture_b.clone();\n*312│    restart.request();\n 313│\n*314│    // ── Generation 1: the supervisor respawned the brain PROCESS onto the\n 315│    //    SWAPPED binary. New pid, exe_hash == B != A (NEW BYTES RAN). ──\n 316│    let (pid1, _gen1, hash1) = wait_ready(&ready_path, Some(pid0), Duration::from_secs(45))\n 317│        .unwrap_or_else(|| teardown_panic(&stop, \"gen-1 brain never respawned onto the swap\"));\n### daemon_refresh_e2e.rs\n  *1│//! THE-FORKENING W4 — `spt daemon refresh` on a LIVE daemon with a hosted\n  *2│//! endpoint (REQ-DAEMON-REFRESH): the brain generation cycles (new brain pid,\n  *3│//! generation bumped) with NO binary swap, while the broker process, the hosted\n   4│//! harness session, and the endpoint's ONLINE perch all survive untouched — the\n   5│//! apply_staged brain-cycle path minus the swap, driven through the REAL CLI\n   6│//! verb. The recovery story: wedged brain-held state no longer needs a full\n    │...\n  13│//\n *14│// [int->REQ-DAEMON-REFRESH]\n  15│\n  16│use std::path::{Path, PathBuf};\n  17│use std::process::{Child, Command, Output, Stdio};\n    │...\n  46│\n *47│/// `(pid, generation)` out of `brain.ready`; `None` until it parses.\n  48│fn read_ready(path: &Path) -> Option<(u32, u64)> {\n  49│    let v: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).ok()?).ok()?;\n  50│    Some((\n  51│        v.get(\"pid\")?.as_u64()? as u32,\n *52│        v.get(\"generation\")?.as_u64()?,\n  53│    ))\n  54│}\n  55│\n    │...\n  59│    while Instant::now() < deadline {\n *60│        if let Some((pid, generation)) = read_ready(path) {\n  61│            if was != Some(pid) {\n *62│                return Some((pid, generation));\n  63│            }\n  64│        }\n  65│        std::thread::sleep(Duration::from_millis(50));\n    │...\n  75│#[test]\n *76│fn daemon_refresh_cycles_brain_while_hosted_endpoint_survives() {\n  77│    let now_ms = || {\n  78│        std::time::SystemTime::now()\n  79│            .duration_since(std::time::UNIX_EPOCH)\n    │...\n 182│\n*183│    // ── (5) THE VERB: `spt daemon refresh` — the production CLI path. ──\n*184│    let refresh = {\n 185│        let mut cmd = Command::new(&spt_bin);\n 186│        cmd.no_window()\n*187│            .args([\"daemon\", \"refresh\"])\n 188│            .env(\"SPT_HOME\", home.path());\n 189│        output_bounded(cmd, Duration::from_secs(30))\n 190│    };\n*191│    let refresh_stdout = String::from_utf8_lossy(&refresh.stdout).to_string();\n*192│    let refresh_stderr = String::from_utf8_lossy(&refresh.stderr).to_string();\n 193│\n 194│    // ── (6) The brain must CYCLE: a new pid stamps brain.ready with a bumped\n*195│    //    generation (no binary swapped anywhere). ──\n 196│    let cycled = wait_ready_not(&ready_path, Some(brain_pid_before), Duration::from_secs(30));\n 197│\n*198│    // The broker process must be the SAME OS process (never restarted).\n 199│    let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);\n 200│\n 201│    // The hosted endpoint survives: harness alive + perch back/still ONLINE.\n    │...\n 216│    eprintln!(\n*217│        \"=== W4 daemon refresh: run_ok={} online={online} harness_pid={harness_pid:?} \\\n*218│         refresh_ok={} brain_before={brain_pid_before}/g{gen_before} cycled={cycled:?} \\\n 219│         broker_alive={broker_still_running} harness_alive_after={harness_alive_after} \\\n*220│         still_online={still_online} ===\\n--- refresh stdout ---\\n{refresh_stdout}\\n\\\n*221│         --- refresh stderr ---\\n{refresh_stderr}\\n=== endpoint run stderr ===\\n{run_stderr}\\n\\\n 222│         === brain stderr ===\\n{brain_stderr}\",\n 223│        run.status.success(),\n### drive_e2e.rs\n 221│    // and poll — EMPTY. The crash-proof token-stamp leg (a frame stamped under\n*222│    // the dead generation is inert even if the clear never ran) is the drivehub\n*223│    // unit `dead_generation_frame_is_never_served`; here we prove the live break.\n 224│    assert!(drive(\"stick\", \"x=0.7\").status.success()); // a pending frame under token_a\n 225│    let shell = spt_runtime::registry::resolve_option(&perch::adapters_dir(), \"mock-shell\")\n 226│        .unwrap()\n### endpoint_autostart_e2e.rs\n  *1│//! F-038 W5 — REQ-ENDPOINT-AUTOSTART (restart-brings-it-up acceptance).\n   2│//!\n   3│//! The gap this locks down (autostart.rs header, deployah field-confirm\n   4│//! 2026-07-10): a gateway-class endpoint is infra — the phone treats `mobile-gw`\n    │...\n   8│//! runs on a boot background thread (`replay_startup_endpoints`, after broker\n  *9│//! bind) so a restart brings the endpoint back up on its own — a FRESH session\n  10│//! (shape (a): a startup default, not a session restore).\n  11│//!\n  12│//! This gate proves the FIX end to end by driving the REAL `spt` binary across a\n *13│//! full daemon RESTART, all inside a fully SPT_HOME-isolated home (its own\n  14│//! daemon, never the operator's live one — the idle_edge_drain_e2e isolation\n  15│//! precedent):\n  16│//!   1. a REAL daemon (A) hosts a REAL spt-hosted `cc` endpoint;\n    │...\n  59│/// anywhere in the body still tears the isolated daemon/harness/psyche tree down\n *60│/// (the restart flow has many more panic points than a single-daemon gate).\n  61│struct Reaper {\n  62│    pids: Mutex<Vec<u32>>,\n  63│}\n    │...\n 123│/// superseded by the in-process redirect). Reads the whole sink so the token is\n*124│/// found wherever the current generation wrote it.\n 125│fn read_logs_sink(home: &Path) -> String {\n 126│    let dir = home.join(\"logs\");\n 127│    let mut out = String::new();\n    │...\n 149│#[test]\n*150│fn saved_endpoint_replays_on_daemon_restart() {\n 151│    let reaper = Reaper::new();\n 152│\n 153│    // ── (1) Isolated, NET-LESS SPT_HOME. ──\n    │...\n 259│    // the fresh replay must supersede), then reap the run-A harness + psyche so\n*260│    // the restart brings a genuinely NEW session up, not a survivor.\n 261│    let rec_a = spt_store::info::read_info(&self_perch);\n 262│    let session_a = rec_a.as_ref().map(|r| r.session_id.clone());\n 263│    if let Some(pid) = spt_store::info::read_pid(&self_perch) {\n    │...\n 287│\n*288│    // ── (6) Daemon B — the RESTART. NO `endpoint run`. Its boot replay thread\n 289│    //    (after broker bind) must bring `gwauto` back up on its own. ──\n 290│    let daemon_b_log = home.path().join(\"daemon-b.stderr.log\");\n 291│    let log_b = std::fs::File::create(&daemon_b_log).expect(\"create daemon B log\");\n    │...\n 322│        // Done once the loud token is present AND the perch carries a fresh\n*323│        // (post-restart) session id distinct from run A's.\n 324│        let fresh = matches!((&session_a, &session_b), (Some(a), Some(b)) if !b.is_empty() && a != b);\n 325│        if autostart_line.is_some() && fresh {\n 326│            break;\n    │...\n 342│    eprintln!(\n*343│        \"=== F-038 W5 AUTOSTART-ON-RESTART GATE: online_a={online_a} run_ok={} \\\n 344│         saved_token={} session_a={session_a:?} session_b={session_b:?} \\\n 345│         autostart_line={autostart_line:?} ===\\n=== endpoint run stderr ===\\n{run_stderr}\\n\\\n 346│         === daemon.json after --save ===\\n{cfg_after_save}\\n\\\n    │...\n 373│        online_a,\n*374│        \"PRECONDITION: the cc endpoint must bind ONLINE under daemon A before restart\"\n 375│    );\n 376│    // (a) The save fired loud AND landed in daemon.json.\n 377│    assert!(\n    │...\n 387│        brain_b.is_some(),\n*388│        \"PRECONDITION: daemon B (the restart) must come up with a fresh brain.\\n\\\n 389│         === daemon B stderr ===\\n{daemon_b_stderr}\"\n 390│    );\n*391│    // (b) THE KEYSTONE: the restart's boot replay brought the saved endpoint back\n 392│    //     up on its own — the loud ENDPOINT_AUTOSTART:<id> token is present in\n 393│    //     daemon B's stderr, emitted only on a successful launch_harness_brokered_in.\n 394│    let line = match autostart_line {\n    │...\n 406│    // (c) A replay is a FRESH session (shape (a): a startup default, not a\n*407│    //     restore) — the post-restart perch must carry a session id distinct from\n 408│    //     run A's.\n 409│    if let (Some(a), Some(b)) = (&session_a, &session_b) {\n 410│        assert!(\n### listen_seed_retry_e2e.rs\n  10│//! 2. **Session-id fallback** (REQ-LISTEN-SESSION-ID-FALLBACK): a session whose\n *11│//!    ephemeral seed is GONE (late go-live / post-daemon-restart) binds via\n  12│//!    `api listen <id> --session-id <sid>` — the loud SID_BIND marker path.\n  13│//!\n  14│//! Both drive the real binary with an explicit `--adapter mock` (bin-only crate:\n    │...\n 166│// [int->REQ-LISTEN-SESSION-ID-FALLBACK] F-034 leg c: a session with NO live seed\n*167│// (its ephemeral SessionStart seed expired / a daemon restart dropped it) binds via\n 168│// `api listen <id> --session-id <sid>`. The seed path yields NO_SEED, the fallback\n 169│// binds from the sid (loud SID_BIND marker), and the perch carries the sid. This is\n*170│// the post-daemon-restart shape — the seed is simply absent.\n 171│#[test]\n 172│fn no_seed_session_binds_via_session_id_fallback() {\n 173│    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());\n### live_adapt_translation_swap_e2e.rs\n   6│//! `Brain::adapter_apply` → stop the resident translation child (frees the OS lock)\n  *7│//! → CRC-swap the install dir → restart the translation from the new manifest path\n   8│//! — and the previously-LOCKED install-dir binary is overwritten with the NEW bytes\n  *9│//! WHILE the endpoint/session keeps running untouched (brain-parity, no restart).\n  10│//!\n  11│//! Real `spt daemon run` broker + supervised brain + a REAL dummy-harness\n  12│//! `[session.self]` session + a REAL translation binary (`translate_proof_fixture`,\n    │...\n 108│#[test]\n*109│fn adapter_apply_swaps_locked_translation_binary_without_restarting_endpoint() {\n 110│    // Serialize against the sibling tests — shared process-global SPT_HOME.\n 111│    let _serial = E2E_LOCK.lock().unwrap_or_else(|e| e.into_inner());\n 112│    let now_ms = || {\n    │...\n 240│    //    This proves the binary IS locked — the exact condition the daemon-coordinated\n*241│    //    apply must work around (stop child → swap → restart). On Unix this won't lock,\n 242│    //    so we skip the pre-assert and just proceed to the apply. ──\n 243│    #[cfg(windows)]\n 244│    let direct_overwrite_locked = {\n    │...\n 286│    //    broker stops the resident translation child (frees the lock), CRC-swaps the\n*287│    //    install dir from staging, restarts the translation from the new path. ──\n 288│    let mut brain = Brain::cold_start(&broker_socket_name(), now_ms())\n 289│        .expect(\"connect a Brain to the live broker for adapter_apply\");\n 290│    let apply_result = brain.adapter_apply(&AdapterApplyReq {\n    │...\n 299│    //        LANDED on the previously-locked binary);\n*300│    //    (b) the session/endpoint is STILL alive (no restart — brain-parity): the\n 301│    //        harness pid is alive AND the perch is still ONLINE. ──\n 302│    let hash_after = file_hash(&xlate_install);\n 303│    let swap_landed = hash_after.is_some()\n    │...\n 377│        \"REQ-ADAPTER-LIVE-UPDATE: Brain::adapter_apply must return Ok (the broker stopped \\\n*378│         the resident translation child, CRC-swapped the install dir, and restarted the \\\n 379│         translation) — got {:?}.\\n=== brain stderr ===\\n{brain_stderr}\",\n 380│        apply_result.err().map(|e| e.to_string())\n 381│    );\n    │...\n 392│\n*393│    // Brain-parity: the endpoint NEVER restarted across the live update.\n 394│    assert!(\n 395│        harness_alive_after,\n 396│        \"REQ-ADAPTER-LIVE-UPDATE (brain-parity): the harness session must STILL be ALIVE \\\n 397│         after the live update — only the translation child cycles, the endpoint never \\\n*398│         restarts. harness_pid={harness_pid:?} was dead post-apply.\\n\\\n 399│         === brain stderr ===\\n{brain_stderr}\"\n 400│    );\n 401│    assert!(\n    │...\n 792│    );\n*793│    // Brain-parity: the composite endpoint never restarted across the live update.\n 794│    assert!(\n 795│        harness_alive_after,\n 796│        \"REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP (brain-parity): the cc:prof harness must STILL be \\\n### n1_compat.rs\n   6│//! broker… the single most update-frequency-sensitive invariant\"). D3-2 added\n  *7│//! the first versioned spawn-time field — `--generation` / `--start-reason` —\n   8│//! so this is the first field with an N-1 window to assert.\n   9│//!\n  10│//! **What this scaffold proves now (single binary, both roles):** the current\n    │...\n  14│//!     shape an older broker that predates D3-2 would spawn → the brain must\n *15│//!     default (generation 0 / `cold`), never die on an arity/unknown-arg error\n  16│//!     before it can rehydrate (KH-2.3's named failure mode); and\n *17│//!   - the **stamped** new-broker invocation (`daemon brain --generation N\n  18│//!     --start-reason R`).\n  19│//!\n  20│//! A clap parse failure exits IMMEDIATELY with usage code 2; a successful parse\n    │...\n  83│    // 1. BARE old-broker invocation — the pre-D3-2 spawn shape. Must parse\n *84│    //    (default generation 0 / cold) and reach the connect loop, never exit 2.\n  85│    {\n  86│        let home = tempfile::tempdir().unwrap();\n  87│        let mut brain = spawn_brain(&spt_bin, home.path(), &[]);\n    │...\n 104│            home.path(),\n*105│            &[\"--generation\", \"9\", \"--start-reason\", \"update\"],\n 106│        );\n 107│        let alive = still_running_after(&mut brain, grace);\n 108│        let _ = brain.kill();\n    │...\n 111│            alive,\n*112│            \"stamped `daemon brain --generation N --start-reason R` must parse + reach the \\\n 113│             connect loop\"\n 114│        );\n 115│    }\n### n1_pairing.rs\n  19│//! window: its broker spawns its own brain with the *bare* pre-D3-2 argv (no\n *20│//! `--generation` / `--start-reason`), and the current brain accepts both shapes.\n  21│//! **Advance rule:** the pin advances to the latest PUBLISHED split-era release\n  22│//! once one exists — so post-v0.4.0 this gate automatically tests the real fleet\n  23│//! N-1, not a frozen D1 ref.\n    │...\n 126│            \"brain\",\n*127│            \"--generation\",\n 128│            \"99\",\n 129│            \"--start-reason\",\n 130│            \"update\",\n### projindex_writer_e2e.rs\n   7│//! daemon start (index published from nothing, batched counters on the status\n  *8│//! surface) → daemon restart (warm start: unchanged generation performs NO\n   9│//! scan, the persisted snapshot is byte-untouched) → the five invalidation\n  10│//! event classes fired through their PRODUCTION funnels (the store's own\n  11│//! nudges for context/rename/fork/purge via the real CLI verbs / ContextStore\n    │...\n  52│\n *53│/// `(pid, generation)` out of `brain.ready`; `None` until it parses.\n  54│fn read_ready(path: &Path) -> Option<(u32, u64)> {\n  55│    let v: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).ok()?).ok()?;\n *56│    Some((v.get(\"pid\")?.as_u64()? as u32, v.get(\"generation\")?.as_u64()?))\n  57│}\n  58│\n  59│/// Poll `brain.ready` until it holds a pid different from `was`, up to `budget`.\n    │...\n  62│    while Instant::now() < deadline {\n *63│        if let Some((pid, generation)) = read_ready(path) {\n  64│            if was != Some(pid) {\n *65│                return Some((pid, generation));\n  66│            }\n  67│        }\n  68│        std::thread::sleep(Duration::from_millis(50));\n    │...\n 186│    );\n*187│    assert!(!cold.source_generation.is_empty(), \"fingerprint stamped\");\n 188│\n 189│    // The observability surface rides `daemon status --json` (index presence\n 190│    // alone is not health), and the batched counters are on it.\n    │...\n 202│        .expect(\"status carries the project_index observability block\");\n*203│    assert_eq!(pi[\"source_generation\"], cold.source_generation.as_str());\n 204│    assert_eq!(pi[\"endpoints\"], 3);\n 205│    assert_eq!(\n 206│        pi[\"last_cycle\"][\"branch_enumerations\"], 1,\n    │...\n 209│\n*210│    // ── (3) WARM START: restart the daemon over the unchanged store. ──\n 211│    let _ = {\n 212│        let mut cmd = Command::new(&spt_bin);\n 213│        cmd.no_window()\n    │...\n 242│    // The persisted index is immediately readable (it was never removed), and\n*243│    // the boot reconcile over the unchanged generation performs NO scan and NO\n 244│    // derivation — and does not rewrite the snapshot.\n 245│    // [int->REQ-PROJECT-INDEX-WRITER]\n 246│    let warm_deadline = Instant::now() + Duration::from_secs(20);\n    │...\n 252│            if v[\"last_run_ms\"].as_u64().unwrap_or(0) > run_before\n*253│                && !v[\"pending_refresh\"].as_bool().unwrap_or(true)\n 254│            {\n 255│                break v;\n 256│            }\n    │...\n 267│        warm_stats[\"last_cycle\"][\"tree_scans\"], 0,\n*268│        \"warm start with an unchanged generation performs NO scan\"\n 269│    );\n 270│    assert_eq!(warm_stats[\"last_cycle\"][\"derivations\"], 0);\n 271│    assert_eq!(\n    │...\n 273│        bytes_before,\n*274│        \"the unchanged snapshot is byte-untouched across the restart\"\n 275│    );\n 276│\n 277│    // ── (4) EVENT INVALIDATIONS against the LIVE daemon. ──\n    │...\n 307│    cs.commit_project(\"beta\", \"ep2 slice\").unwrap().unwrap();\n*308│    wait_index(&index_path, Duration::from_secs(15), \"context commit → new generation\", |i| {\n*309│        i.source_generation != cold.source_generation\n 310│            // ep2's RENDERED attribution stays origin-cwd (higher precedence);\n*311│            // the membership refresh is visible through the generation move.\n 312│            && i.endpoints.get(\"ep2\").is_some_and(|e| e.source.as_deref() == Some(\"origin-cwd\"))\n 313│    });\n 314│\n### resume_no_control_steal_e2e.rs\n  97│\n *98│use spt_daemon::brainproc::{ready_generation_at, supervise_brain, BrainRestart, StartReason, TrialEnv};\n  99│use spt_daemon::codec::{read_frame, write_frame};\n 100│use spt_daemon::endpoint::broker_socket_name;\n 101│use spt_daemon::frame::{Envelope, Role};\n    │...\n 302│    }\n*303│    fn ready_generation(&self) -> Option<u64> {\n*304│        ready_generation_at(&self.ready_path)\n 305│    }\n 306│    fn clear_ready(&self) {\n 307│        let _ = std::fs::remove_file(&self.ready_path);\n    │...\n 399│        thread::spawn(move || {\n*400│            let restart = BrainRestart::new();\n 401│            supervise_brain(\n 402│                &stop_sup,\n*403│                &restart,\n 404│                Duration::from_millis(50),\n 405│                env_sup.as_ref(),\n 406│                Duration::from_secs(60),\n    │...\n 419│                            \"brain\",\n*420│                            \"--generation\",\n 421│                            &gen.to_string(),\n 422│                            \"--start-reason\",\n 423│                            reason.as_arg(),\n### trial_drain_drive_e2e.rs\n   3│//! production `run_brain` self-drives the broker's controller-liveness reap so a\n  *4│//! hard-killed prior generation's black-holed LOCAL controller conn cannot strand\n   5│//! the promotion DRAINED gate. This is the process-level upgrade of the in-process\n   6│//! `crates/spt-daemon/tests/false_promote.rs` (REQ-UPDATE-PROMOTE-DRAINED): there the\n   7│//! candidate is a fake `sleep`/`ping` and the wedge is drained by an EXTERNAL\n    │...\n  23│//! candidate DRIVES the live older broker's already-shipped reap and un-strands its\n *24│//! OWN trial with no coordinated broker restart.\n  25│//!\n  26│//! ## Why the wedge is established AFTER the candidate boots (the resume paradox)\n  27│//!\n    │...\n  39│//! hosts no PTY sessions; brainproc.rs:184) and the wedged controller was a\n *40│//! prior-generation conn resume did NOT cover. This rig reproduces that faithfully:\n  41│//! the candidate boots + resumes against an EMPTY session set, THEN conn A spawns a\n  42│//! flood session and black-holes as its by:None controller — a session the boot-once\n  43│//! resume never covered, exactly the field's un-resumed wedge. A black-holed by:None\n    │...\n  53│//! The real subprocess stamps `brain.ready` autonomously the instant it boots — before\n *54│//! the test can establish the wedge. So the rig's [`TrialEnv::ready_generation`]\n  55│//! WITHHOLDS the candidate's readiness from `run_trial` until the test ARMS it (after\n  56│//! the old-gen wedge is up + matured), then reads the REAL `brain.ready` file. This is\n  57│//! the process-level analog of false_promote's scripted-ready `Mutex`: it guarantees\n    │...\n 108│use spt_daemon::brain::Brain;\n*109│use spt_daemon::brainproc::{ready_generation_at, supervise_brain, BrainRestart, StartReason, TrialEnv};\n 110│use spt_daemon::codec::{read_frame, write_frame};\n 111│use spt_daemon::endpoint::broker_socket_name;\n 112│use spt_daemon::frame::{Envelope, Role};\n    │...\n 292│    }\n*293│    fn ready_generation(&self) -> Option<u64> {\n 294│        // LATCH: withhold the real subprocess's readiness from run_trial until the\n 295│        // test has armed the old-gen wedge, so the DRAINED gate has a matured wedge to\n 296│        // block on when readiness is first seen (never a trivial no-wedge promote).\n    │...\n 299│        }\n*300│        ready_generation_at(&self.ready_path)\n 301│    }\n 302│    fn clear_ready(&self) {\n 303│        let _ = std::fs::remove_file(&self.ready_path);\n    │...\n 373│        thread::spawn(move || {\n*374│            let restart = BrainRestart::new();\n 375│            supervise_brain(\n 376│                &stop_sup,\n*377│                &restart,\n 378│                Duration::from_millis(50),\n 379│                env_sup.as_ref(),\n 380│                Duration::from_secs(60), // generous window — covers a slow-CI boot (eaten before the\n    │...\n 394│                            \"brain\",\n*395│                            \"--generation\",\n 396│                            &gen.to_string(),\n 397│                            \"--start-reason\",\n 398│                            reason.as_arg(),\n    │...\n 417│    //    resume, so resume never covered it. dispatch_spawn pre-attaches A as the\n*418│    //    session's LOCAL controller (by = None) — the old-generation-conn identity.\n 419│    //    A then goes silent: the child's ONE burst fills A's recv buffer, A's broker-side\n 420│    //    controller writer parks BLOCKED in write_frame (`write_blocked_since` set), and\n 421│    //    with no further output the orthogonal 5s output-driven detach never fires — so\n### tunnel_e2e.rs\n  14│//! tunnel (a post-break resolve finds none); (3) no-stale-stream-across-relink (R1)\n *15│//! — bytes pending in the pre-break generation are NEVER surfaced to the relinked\n  16│//! instance's fresh tunnel.\n  17│//\n  18│// [int->REQ-SHELL-4]\n    │...\n 252│    // relink (R1). Send a pending payload, DON'T drain it, break the link, relink to\n*253│    // a fresh generation, and confirm the relinked tunnel never serves the stale\n 254│    // bytes.\n 255│    shell_send(&token_a, b\"STALE-PRE-BREAK-BYTES\".to_vec()); // pending in the owner end's ring\n 256│    let shell = spt_runtime::registry::resolve_option(&perch::adapters_dir(), \"mock-shell\")\n    │...\n 280│    // Drain the fresh owner end a few times — the stale \"STALE-PRE-BREAK-BYTES\" from\n*281│    // the dead generation must NEVER appear (no-stale-stream-across-relink, R1).\n 282│    let mut fresh = Vec::new();\n 283│    for _ in 0..30 {\n 284│        let out = spt(&[\"shell\", \"tunnel\", \"Scout\", \"recv\", \"--owner\", \"doyle\"]);\n### twohost_cli.rs\n  31│//!    honest both-sockets death: seed + broker die with the process) → A's\n *32│//!    child paints the reconnect banner → B's daemon restarts and the ticker\n  33│//!    respawns → the viewport reheals on the production loop.\n  34│//!\n  35│//! ## Rendezvous (differs from the ladder — deliberately)\n    │...\n 245│    fn start(home: &Path, spt: &Path, tag: &str) -> DaemonGuard {\n*246│        // A prior generation's ready-file must never satisfy this bring-up.\n 247│        let ready = home.join(\"brain.ready\");\n 248│        let _ = std::fs::remove_file(&ready);\n 249│        let log = home.join(format!(\"brain-{tag}.stderr.log\"));\n    │...\n 695│    daemon.stop();\n*696│    println!(\"GATED role B: daemon DOWN (honest stop) — restarting\");\n 697│    let daemon2 = DaemonGuard::start(home.path(), &spt, \"b2\");\n*698│    // Re-anchor the ticker perch on generation 2's pid BEFORE anything\n*699│    // re-advertises: the old anchor died with generation 1, and a dead-pid row\n 700│    // would replicate Offline to A mid-reheal — unroutable for the re-dial.\n 701│    info::mutate_info(&perch_rc, |rec| {\n 702│        rec.pid = info::PidValue::Numeric(daemon2.broker_pid())\n    │...\n 712│    signal(&rig, &rig.b_hex(), ID_RC, SIG_BYE);\n*713│    // MUTUAL-HOLD (attempt-4 red): the bye rides THIS generation-2 daemon's\n 714│    // outbound pump, and a fresh daemon's dial to A goes through discovery —\n 715│    // seconds to minutes, not a sleep. Hold the daemon serving until the bye\n 716│    // observably arrived: A ACKs by DISMISSING the row, and the dismissed=true"},"isError":false,"timestamp":1784247434352}}
{"type":"message","id":"50b36534","parentId":"59f7daa9","timestamp":"2026-07-17T00:17:14.376Z","message":{"role":"toolResult","toolCallId":"call_oz5x5LdbkEVCdhencwlvorLZ|fc_099a7567dadef391016a59748a2304819b88d3478486352189","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/tests/\n## access.rs\n 84|fn wait_for_stream(brain: &mut Brain) -> (u64, String) {\n*85|    for _ in 0..400 {\n 86|        let reply = brain.net_streams().expect(\"net-streams\");\n 87|        if let Some(s) = reply.streams.first() {\n 88|            return (s.stream_id, s.remote_id_hex.clone());\n## attach.rs\n 9|//! - **remote drive**: keystrokes sent from B arrive at A's PTY through the\n*10|//!   journaled input path, and the session's output streams back to B as\n 11|//!   sequenced records — B sees its own marker echoed by A's child;\n 12|//! - **origin is transport truth** (KNOWN-HAZARDS 7.5): the serve loop gates\n 13|//!   on the stream table's handshake-proven `remote_id_hex`;\n...\n 119|fn wait_for_stream(brain: &mut Brain) -> (u64, String) {\n*120|    for _ in 0..400 {\n 121|        let reply = brain.net_streams().expect(\"net-streams\");\n 122|        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 123|            return (s.stream_id, s.remote_id_hex.clone());\n...\n 137|/// Given an acquired `conn_id` on `operator` and the `target` broker connection\n*138|/// holding `session_id` for `endpoint`: open the attach stream, serve it on\n 139|/// `target` under `expected_origin` (asserted == the stream table's proven\n 140|/// origin), drive `marker` through the PTY and render it back over the wire,\n*141|/// then detach and confirm the serve loop ended `Detached` (session outlives).\n 142|/// Returns the rendered viewport bytes + the (still-live) target brain so the\n*143|/// caller can assert session survival and clean up.\n 144|#[allow(clippy::too_many_arguments)]\n 145|fn attach_drive_detach(\n 146|    operator: &mut Brain,\n...\n 154|) -> (Vec<u8>, Brain) {\n*155|    let stream_b = request_attach(operator, conn_id, session_id, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n 156|    operator\n 157|        .net_stream_subscribe(stream_b, 0)\n 158|        .expect(\"operator subscribe\");\n...\n 191|\n*192|    // Detach: the viewport ends with the session still running.\n 193|    operator\n 194|        .net_stream_send(stream_b, &[], None, true)\n 195|        .expect(\"detach\");\n...\n 294|// streams back. This is the W1 gate proof — \"bring up + attach a real harness\n*295|// endpoint\" over the byte-identical cross-node attach pump; the session outlives\n 296|// the viewport (detach never kills it).\n 297|#[test]\n 298|fn spt_hosted_bringup_then_cross_node_attach_drives_the_pty() {\n...\n 304|\n*305|    // Bring up an spt-hosted harness on A. Its `[session.self]` is a real echo\n 306|    // binary standing in for a harness — the bringup spawns it into broker A's\n 307|    // PTY exactly as it would a `claude` command.\n 308|    #[cfg(unix)]\n...\n 313|        \"[adapter]\\nname = \\\"mock\\\"\\nkind = \\\"harness\\\"\\nversion = \\\"1\\\"\\n\\\n*314|         min_spt_core_version = \\\"0\\\"\\n\\n[session.self]\\ncommand = '{self_cmd}'\\n\"\n 315|    ))\n 316|    .expect(\"harness manifest\");\n 317|    let spawned = spt_daemon::harnesshost::launch_harness_brokered_in(\n...\n 321|\n*322|    // Operator on B attaches CROSS-NODE to the brought-up session.\n 323|    let mut target = connect_retry(&name_a);\n 324|    let a_addr = target.net_status().expect(\"a status\").addr;\n 325|    let mut operator = connect_retry(&name_b);\n...\n 332|    let stream_b =\n*333|        request_attach(&mut operator, conn.conn_id, spawned.session_id, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n 334|    operator\n 335|        .net_stream_subscribe(stream_b, 0)\n 336|        .expect(\"operator subscribe\");\n...\n 369|\n*370|// [unit->REQ-INST-8] drive an agent('s session) on machine A from machine B:\n 371|// B's keystrokes reach A's real PTY child over the wire (journaled input\n 372|// path), the child's output streams back as sequenced records, and B renders\n 373|// its own marker echoed remotely. The serve gate sees the handshake-proven\n*374|// origin; B detaching ends the viewport with the session still running on A.\n 375|#[test]\n 376|fn remote_attach_drives_a_real_pty_cross_daemon() {\n 377|    init_home();\n...\n 385|    let sid = target\n*386|        .spawn_session(echo_spawn_req())\n 387|        .expect(\"spawn echo child\");\n 388|    let a_addr = target.net_status().expect(\"a status\").addr;\n 389|\n...\n 417|// [int->REQ-RC-1] LOCAL attach rides the byte-identical pump (M12 W1.5, B1):\n*418|// a single broker spawns a real PTY session; the operator acquires a\n 419|// broker-minted in-process LOOPBACK conn (`net_dial_loopback` — NOT a QUIC\n 420|// self-dial, which iroh refuses) and drives the PTY through the SAME\n 421|// `serve_attach`+render pump a cross-node operator uses. The proof that \"both\n...\n 426|// origin is THIS node's own id, gating same-node (access short-circuit Allow in\n*427|// production; default-open in this hermetic home — both Allow). The session\n 428|// outlives the viewport.\n 429|#[test]\n 430|fn local_attach_via_loopback_conn_rides_the_same_pump() {\n...\n 440|    let sid = target\n*441|        .spawn_session(echo_spawn_req())\n 442|        .expect(\"spawn echo child\");\n 443|\n 444|    // The operator acquires the broker's loopback conn; its `remote_id_hex` is\n...\n 468|// [unit->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT] the replay-vs-forward deadlock pin:\n*469|// when the session's OutputLog ring is ALREADY NON-EMPTY at attach (an\n*470|// already-producing session — exactly the endpoint-run shape, whose transient\n 471|// cold-start spawn conn drops after `Spawned`), `become_controller` replays MANY\n 472|// frames into the SERVING brain while `serve_attach` must forward each back to the\n 473|// operator. Before the two-conn split both rode ONE broker IPC connection, so the\n## brain_decouple.rs\n 10|//! `CONTROLLER_WRITE_DEADLINE`) fires only on NEW output, and `reap_dead_controller`\n*11|//! only caught an EXITED writer — so a controller BLOCKED on an idle session was\n 12|//! evicted by neither: control latched, reattach was refused, `rc --take` hung,\n 13|//! `daemon status` still read healthy.\n 14|//!\n...\n 247|        if f.kind == KIND_SPAWNED {\n*248|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 249|        }\n 250|    };\n 251|\n 252|    // ── Remote controller R: a REMOTE operator (by=Some) takes control of the\n*253|    //    (locally-spawned, undriven) session, then STOPS reading — the suspended-\n 254|    //    brain analog. R MUST be a remote controller: a second remote's Control\n*255|    //    subscribe against a DIFFERENT-remote-driven session busy-refuses PRE-W2\n 256|    //    (the wedge), so only the stall-evict can free the slot — this is what\n 257|    //    isolates the fix from the pre-existing \"a remote takes an UNDRIVEN\n*258|    //    (local-only) session\" silent-control path. R's conn is held open (never\n 259|    //    read from) on its own thread so the broker's writer for R parks in\n 260|    //    write_frame exactly as against a suspended brain. ──\n 261|    let rname = name.clone();\n...\n 441|\n*442|/// One `KIND_SESSIONS` round-trip, returning the full reply. The dispatch runs\n 443|/// `reap_dead_controller` → the time-based `stall_evict_controller`, so polling\n 444|/// this both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 445|/// Bounded thread + channel (a wedge surfaces as a timeout, not a hang), like the\n...\n 448|#[cfg(windows)]\n*449|fn query_sessions(name: &str, deadline: Duration) -> Option<spt_daemon::msg::SessionsReply> {\n 450|    let name = name.to_string();\n 451|    let (tx, rx) = std::sync::mpsc::channel();\n 452|    thread::spawn(move || {\n 453|        let mut c = connect(&name);\n*454|        send(&mut c, spt_daemon::msg::KIND_SESSIONS, serde_json::Value::Null);\n 455|        loop {\n 456|            match read_frame(&mut c) {\n*457|                Ok(f) if f.kind == spt_daemon::msg::KIND_SESSIONS_REPLY => {\n*458|                    let r: spt_daemon::msg::SessionsReply =\n 459|                        serde_json::from_value(f.payload).unwrap();\n 460|                    let _ = tx.send(r);\n 461|                    return;\n...\n 496|        if f.kind == KIND_SPAWNED {\n*497|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 498|        }\n 499|    };\n 500|    drop(a);\n...\n 634|    // ── Step 6: exactly ONE logical stall eviction, observed via an INDEPENDENT\n*635|    //    DRAINING control-plane connection (the KIND_SESSIONS reap runs the 400 ms\n 636|    //    stall-evict). Record t_release at first observation and freeze the resume\n 637|    //    cursor for the sid row. ──\n 638|    // POISON-BEATS-EVICT (F-039 rider, doyle-ruled 2026-07-10, 3rd CI trip): the\n...\n 651|    while Instant::now() < step6_deadline {\n*652|        let sessions = query_sessions(&name, Duration::from_secs(3));\n 653|        let count = stall_evict_count(&name, Duration::from_secs(3));\n 654|        if count >= 1 {\n 655|            stall_evicts_seen = count;\n 656|            t_release = Some(Instant::now());\n*657|            frozen_cursor = sessions\n 658|                .as_ref()\n*659|                .and_then(|r| r.sessions.iter().find(|s| s.session_id == sid))\n 660|                .map(|s| s.resume_seq)\n 661|                .or_else(|| {\n 662|                    query_sessions(&name, Duration::from_secs(3)).and_then(|r| {\n...\n 664|                            .iter()\n*665|                            .find(|s| s.session_id == sid)\n 666|                            .map(|s| s.resume_seq)\n 667|                    })\n 668|                });\n...\n 693|            s,\n*694|            &Envelope::new(spt_daemon::msg::KIND_SESSIONS, serde_json::Value::Null),\n 695|        )\n 696|    });\n 697|    let old_client_write_is_err = old_client_write.as_ref().map(|r| r.is_err());\n## brain_resume_conn_deadlock.rs\n 4|//! Proves the code-read root (docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md), DEAD-PEER\n*5|//! INDEPENDENT: the daemon brain's `resume_sessions` subscribes broker PTY sessions\n 6|//! onto the brain's OWN request/reply conn. A conn's send half is one\n 7|//! `SharedSend = Arc<Mutex<SendHalf>>`; the subscriber writer threads\n 8|//! (`viewer_writer`/`controller_writer`) hold `send.lock()` ACROSS a BLOCKING\n 9|//! `write_frame`, and the dispatch reply path (`send_frame`) needs the SAME lock.\n*10|//! The daemon brain hosts no PTY sessions, so `run_brain` never drains that output —\n*11|//! it reads the conn only during its 500ms-heartbeat `net_status()`/`sessions()`\n*12|//! calls. When an actively-streaming session backs the conn up, the brain's own\n 13|//! subscriber writer blocks holding `send.lock()` → the heartbeat REPLY is starved →\n 14|//! the heartbeat stalls → the brain never drains → SELF-DEADLOCK. That is the field\n 15|//! wedge (both severities: respawn replay-burst + steady-state output backup), with\n...\n 112|\n*113|/// Spawn one flood PTY session; return (session_id, pid, spawner_conn). The conn is\n 114|/// kept alive (the spawner is the local by:None controller) but NOT drained — the\n 115|/// child floods the PTY regardless, so the broker's ring keeps advancing and the\n 116|/// brain's viewer subscription keeps receiving live output.\n...\n 127|            let s: Spawned = serde_json::from_value(f.payload).unwrap();\n*128|            return (s.session_id, s.pid.unwrap_or(0), c);\n 129|        }\n 130|    }\n 131|}\n...\n 133|/// How the probe brain resumes before running the heartbeat: the SHIPPING daemon\n*134|/// path — `CursorOnly` → `resume_session_cursors`, no subscribe. (The hazard path,\n 135|/// `resume_sessions`, is exercised directly by the deterministic zero-subscriber guard\n 136|/// `daemon_resume_leaves_zero_brain_subscribers` below, not through this probe.)\n 137|#[derive(Clone, Copy)]\n...\n 142|/// Drive `run_brain`'s heartbeat pattern on a fresh `Brain` conn: resume per `mode`,\n*143|/// then loop `net_status()` + `sessions()` — the exact per-tick IPC — incrementing a\n 144|/// shared counter per COMPLETED tick, so the caller can measure liveness in a window.\n 145|/// The probe NEVER drains subscribed output (it only issues request/reply, exactly like\n 146|/// the real non-draining daemon heartbeat) — so `CursorOnly` faithfully models the\n...\n 162|            ResumeMode::CursorOnly => {\n*163|                let _ = brain.resume_session_cursors();\n 164|            }\n 165|        }\n 166|        while !stop.load(Ordering::Relaxed) {\n...\n 170|            }\n*171|            if brain.sessions().is_err() {\n 172|                return;\n 173|            }\n 174|            ticks.fetch_add(1, Ordering::Relaxed);\n...\n 179|/// How many heartbeat ticks complete in `window` for a probe configured with `mode`.\n*180|/// N flood PTY sessions are already streaming before the probe starts.\n*181|fn heartbeat_ticks_under_flood(mode: ResumeMode, n_sessions: usize, window: Duration) -> u32 {\n 182|    init_home();\n 183|    let name = unique_name();\n 184|    let broker = Broker::bind(&name).expect(\"bind broker\");\n...\n 189|\n*190|    // Stand up N flooding PTY sessions the brain will resume.\n 191|    let mut conns = Vec::new();\n 192|    let mut pids = Vec::new();\n*193|    for i in 0..n_sessions {\n 194|        let (_sid, pid, conn) = spawn_flood(&name, &format!(\"flood-ep-{i}\"));\n 195|        pids.push(pid);\n 196|        conns.push(conn); // hold the spawner controller conns open (undrained)\n...\n 216|/// STEADY-STATE severity — the SHIPPING path: the daemon brain resumes CURSOR-ONLY\n*217|/// (`resume_session_cursors`, no subscribe) while N sessions actively stream, so its\n 218|/// heartbeat stays live. NO dead peer anywhere. RED-first: revert run_brain to the\n*219|/// subscribing `resume_sessions` (or see the regression-guard test below) and this\n 220|/// starves to a standstill.\n 221|// [int->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n 222|#[test]\n...\n 229|        ticks >= 100,\n*230|        \"the daemon's cursor-only resume must keep the heartbeat live under a {N}-session \\\n 231|         flood (got {ticks} ticks in {window:?}). A low count means the SharedSend \\\n 232|         self-deadlock re-appeared — the brain is subscribing a conn it does not drain.\"\n 233|    );\n...\n 236|/// RESPAWN-interleave severity — the SHIPPING path under the update-restart burst: a\n*237|/// fresh brain generation resumes CURSOR-ONLY while N sessions already flood at full\n 238|/// rate (the sessions predate this generation). The new generation's heartbeat — hence\n 239|/// its readiness/promotion — is never starved.\n 240|// [int->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n...\n 265|/// Two settled arms, no timing window:\n*266|///   POSITIVE — the shipping daemon path (`resume_session_cursors`) must leave EVERY\n 267|///   hosted session with ZERO viewers. A resume that instead subscribed the undrained\n 268|///   heartbeat conn re-opens the round-3 SharedSend self-deadlock class; post-r4 it\n 269|///   would instead get that heartbeat conn poisoned/retired by the write watchdog —\n...\n 272|///   takes the hazard path (`resume_sessions`, still present) and MUST raise every\n*273|///   session's viewer count to >= 1, proving `test_session_viewer_count` observes the\n 274|///   very subscription the positive arm forbids (a bare \"assert 0\" could pass vacuously\n 275|///   against an always-0 seam).\n 276|///\n## brain_swap.rs\n 15|//! produced while no brain was attached. Throughout, the child's pid is unchanged\n*16|//! and exactly one session is hosted: the endpoint never noticed the swap.\n 17|//!\n 18|//! **Filename note:** this file is `brain_swap.rs`, NOT `update.rs`, on purpose.\n 19|//! Windows' installer-detection heuristic (UAC) forces elevation for any\n...\n 109|    let sid = brain\n*110|        .spawn_session(echo_spawn_req())\n*111|        .expect(\"spawn session\");\n 112|    let child_pid = broker.session_pid(sid);\n 113|    assert_eq!(brain.generation(), 0);\n 114|\n...\n 147|        1,\n*148|        \"exactly one session across the update\"\n 149|    );\n 150|    assert_eq!(\n 151|        broker.session_pid(sid),\n...\n 173|    // The post-update brain still fully drives the same endpoint: kill it and see\n*174|    // the exit event + reaping — the session was live and owned, not a husk.\n*175|    brain.kill_session().expect(\"kill session\");\n 176|    loop {\n*177|        if let BrokerEvent::Exit { session_id, .. } = brain.read_event().expect(\"read until exit\") {\n 178|            assert_eq!(session_id, sid);\n 179|            break;\n 180|        }\n## broker.rs\n 144|        KIND_KILL,\n*145|        serde_json::to_value(KillReq { session_id: sid }).unwrap(),\n 146|    );\n 147|    let mut exit: Option<ExitEvent> = None;\n 148|    loop {\n...\n 160|    // Disconnect: dropping the single Stream closes the fd, so the broker's\n*161|    // command loop hits EOF and tears the session down. Joining the handler\n 162|    // (via the server thread) lets us assert the teardown happened.\n 163|    drop(conn);\n 164|    server.join().expect(\"server thread\");\n...\n 172|    assert_eq!(exit.session_id, sid);\n*173|    assert_eq!(broker.session_count(), 0, \"session torn down on disconnect\");\n 174|}\n 175|\n 176|fn accumulate_output(frame: &Envelope, out: &mut Vec<u8>) {\n...\n 187|/// inbound message addressed by ENDPOINT ID (`KIND_ENDPOINT_INPUT`) is delivered to\n*188|/// an spt-hosted session ONLY through its translation binary. With NO translation\n 189|/// binary declared (this echo child), idle delivery has no working binary → the\n 190|/// broker acks `delivered=false` and writes NOTHING to the PTY (the caller spools;\n 191|/// raw `payload+\\r` was removed as a delivery path — it did not submit on a modern\n...\n 235|\n*236|    // Endpoint-keyed inject (NO session_id — resolved by endpoint) into a hosted\n 237|    // session that declares NO translation binary: idle delivery is\n 238|    // translation-binary-ONLY, so there is no working binary → the broker acks\n 239|    // delivered=FALSE and writes NOTHING to the PTY (the caller spools).\n...\n 427|/// has EXITED surfaces a prompt Exit (dead-not-reaped → dispatch_subscribe's\n*428|/// try_wait → Exit frame) or a clean \"no such session\" error (already reaped) —\n 429|/// NEVER silence (the infinite-blank hang an attach would otherwise show). Spawn a\n 430|/// child that exits immediately, then poll-subscribe and assert a prompt\n 431|/// non-silent reply.\n 432|// [int->REQ-HAZARD-RC-ATTACH-FAILFAST]\n*433|fn dead_session_subscribe_test() {\n 434|    let name = unique_name();\n 435|    let broker = Broker::bind(&name).expect(\"bind broker\");\n 436|    let server_broker = Arc::clone(&broker);\n...\n 478|            Ok(f) if f.kind == KIND_SPAWNED => {\n*479|                sid = Some(serde_json::from_value::<Spawned>(f.payload).unwrap().session_id);\n 480|                break;\n 481|            }\n 482|            Ok(_) => {}\n...\n 487|\n*488|    // Poll-subscribe: every attempt must yield a prompt Exit or no-such-session\n 489|    // error — never an unanswered (silent) subscribe.\n 490|    let mut answered = false;\n 491|    for _ in 0..100 {\n...\n 525|#[test]\n*526|fn dead_session_subscribe_does_not_hang() {\n 527|    dead_session_subscribe_test();\n 528|}\n 529|\n...\n 578|        \"[adapter]\\nname = \\\"mock\\\"\\nkind = \\\"harness\\\"\\nversion = \\\"1\\\"\\n\\\n*579|         min_spt_core_version = \\\"0\\\"\\n\\n[session.self]\\ncommand = '{self_cmd}'\\n\\n\\\n 580|         [env.SPT_ENDPOINT_ID]\\ndirection = \\\"inject\\\"\\nvalue = \\\"{{id}}\\\"\\n\"\n 581|    );\n 582|    let manifest = spt_runtime::Manifest::from_toml_str(&manifest_toml).unwrap();\n...\n 617|    let mut saw_eid = false;\n*618|    for _ in 0..400 {\n 619|        if String::from_utf8_lossy(&out).contains(\"EID=wall-b\") {\n 620|            saw_eid = true;\n 621|            break;\n...\n 636|    // The send leg (ADR-0022 amendment / REQ-HAZARD-IDLE-SILENT-NONDELIVERY): an\n*637|    // endpoint-keyed inject to a session with NO translation binary SPOOLS — the\n 638|    // broker acks delivered=false and writes NOTHING to the PTY (raw inject removed;\n 639|    // a bare payload+\\r does not submit on a modern TUI). Read the\n 640|    // KIND_ENDPOINT_INJECTED ack on the SENDER conn — it DOES arrive — and assert\n...\n 707|\n*708|/// Spawn the long-lived echo child and return its session id (draining any\n 709|/// interleaved early output into `out`).\n 710|fn spawn_echo(conn: &mut Stream, out: &mut Vec<u8>) -> u64 {\n 711|    send(conn, KIND_SPAWN, serde_json::to_value(echo_spawn_req()).unwrap());\n...\n 759|/// REQ-HAZARD-INPUT-ACK-BACKPRESSURE — the ACK GATE, the crux unit. Against a\n*760|/// REAL broker hosting a REAL spawned PTY session, an `op_id`-carrying input with\n 761|/// `ack = false` produces NO `KIND_APPLIED` frame, while one with `ack = true`\n 762|/// does. This is the broker leg of the fix: `dispatch_input` writes the applied\n 763|/// envelope ONLY when `req.ack`, so the fire-and-forward operator/rc flood never\n...\n 810|        let mut probe_echoed = false;\n*811|        for _ in 0..4000 {\n 812|            if first_applied_op == Some(PROBE_OP) && noack_echoed && probe_echoed {\n 813|                break;\n 814|            }\n...\n 905|    // Drive the send + drain on a worker (the blocking `read_frame` can stall once\n*906|    // steady-state output stops) feeding back (session_id, echo_count). The main\n 907|    // thread bounds it with a watchdog, then reads the AUTHORITATIVE exactly-once\n 908|    // evidence straight off the broker-owned journal.\n 909|    let (tx, rx) = std::sync::mpsc::channel::<(u64, usize)>();\n...\n 1032|        if f.kind == KIND_SPAWNED {\n*1033|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 1034|        }\n 1035|    };\n 1036|\n...\n 1132|    // Send a UNIQUE post-subscribe marker; reading the consumer stream until it is\n*1133|    // ACCEPTED both bounds the read AND proves the session stayed LIVE through the\n 1134|    // double controller swap (input still reaches the PTY, output still flows).\n 1135|    send(\n 1136|        &mut conn,\n...\n 1193|        KIND_KILL,\n*1194|        serde_json::to_value(KillReq { session_id: sid }).unwrap(),\n 1195|    );\n 1196|    loop {\n 1197|        match read_frame(&mut conn) {\n## budget.rs\n 11|//!\n*12|//! A **dormant** seat is a live, state-preserved harness session held warm\n 13|//! by the broker (PTY master + child process + output ring). A **suspended**\n 14|//! seat is that session closed — only the on-disk perch record remains. So:\n 15|//!\n*16|//! 1. spawn N idle sessions through the real broker (the production PTY\n 17|//!    hosting path), labeled per seat;\n 18|//! 2. settle, then sample each child's RSS / handle (fd) count / CPU time,\n 19|//!    twice over an idle window (the delta = idle CPU burn);\n...\n 79|\n*80|    // One probe brain per seat (kill_session targets the brain's own session).\n*81|    let mut seats: Vec<(Brain, u64, u32)> = Vec::new(); // (brain, session, child pid)\n 82|    for i in 0..n {\n 83|        let mut brain = connect_retry(&name);\n 84|        let req = SpawnReq {\n...\n 95|        };\n*96|        let (session, pid) = brain.spawn_session_pid(req).expect(\"spawn seat\");\n 97|        let pid = pid.expect(\"seat child pid\");\n*98|        seats.push((brain, session, pid));\n 99|    }\n 100|\n 101|    thread::sleep(settle);\n...\n 153|\n*154|    // Suspend: close every seat (the session ends; only the perch record\n 155|    // would remain in production). The residual is the daemon baseline.\n 156|    for (brain, _, _) in seats.iter_mut() {\n*157|        let _ = brain.kill_session();\n 158|    }\n 159|    thread::sleep(Duration::from_millis(1_500));\n 160|    let self_after = sample_pid(std::process::id()).unwrap_or_default();\n## conn_blackhole_lifecycle.rs\n 14|//! src/conn.rs` bounded/cancelable/poison-on-failure writes, landed fc048bb),\n*15|//! asserts hertz's FIVE invariants for ONE incident:\n 16|//!\n*17|//!   1. UNRELATED SESSIONS CONTINUE — a second, independent session's viewer keeps\n 18|//!      receiving output throughout the black-hole incident (the fan-out never\n 19|//!      blocks the whole broker on the one wedged conn);\n 20|//!   2. THE BAD PHYSICAL CONNECTION IS CANCELED/CLOSED WITHIN THE BOUND — a write\n...\n 34|//!      PROXIES (this invariant has no direct seam): after retirement the broker\n*35|//!      still answers a control-plane `KIND_SESSIONS` request PROMPTLY (no lock\n 36|//!      is held by the retired writer), `conn_handler_count()` returns to the\n 37|//!      unrelated-viewer floor without the client dropping its end (no task\n 38|//!      lingers), AND both the\n*39|//!      unrelated session's viewer and the fresh viewer flow output (the shared\n 40|//!      fan-out lock is free). The conjunction of these three is the strongest\n 41|//!      deterministic proxy available for \"nothing is still owned.\"\n 42|//!\n...\n 53|//! not have, which are exactly hertz's invariants (1) and (4): a SECOND\n*54|//! UNRELATED session whose viewer keeps ticking across the incident, and a fresh\n 55|//! VIEWER attach (not just a controller take) proven to receive output.\n 56|//!\n 57|//! ## Why Windows-mandatory (`#![cfg(windows)]` — the whole file)\n...\n 89|use spt_daemon::msg::{\n*90|    decode_bytes, encode_bytes, InputReq, OutputEvent, SessionsReply, SpawnReq, Spawned,\n 91|    StallEvictsReply, SubscribeOutcome, SubscribeReq, SubscribedReply, KIND_INPUT, KIND_OUTPUT,\n*92|    KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN, KIND_SPAWNED, KIND_STALL_EVICTS,\n 93|    KIND_STALL_EVICTS_REPLY, KIND_SUBSCRIBE, KIND_SUBSCRIBED,\n 94|};\n 95|use spt_daemon::transport::{send_hello, LocalSocketTransport};\n...\n 182|\n*183|/// Spawn a PTY child from a fresh conn and return `(spawn_conn, session_id)`.\n*184|fn spawn_session(name: &str, req: SpawnReq) -> (Stream, u64) {\n 185|    let mut c = connect(name);\n 186|    send(&mut c, KIND_SPAWN, serde_json::to_value(req).unwrap());\n 187|    let sid = loop {\n...\n 189|        if f.kind == KIND_SPAWNED {\n*190|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 191|        }\n 192|    };\n 193|    (c, sid)\n...\n 221|\n*222|/// One `KIND_SESSIONS` round-trip, returning the reply. The dispatch runs the\n 223|/// `reap_dead_controller` → time-based `stall_evict_controller`, so polling this\n 224|/// both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 225|/// Bounded thread + channel. Verbatim sibling of the r4 gate helper.\n*226|fn query_sessions(name: &str, deadline: Duration) -> Option<SessionsReply> {\n 227|    let name = name.to_string();\n 228|    let (tx, rx) = std::sync::mpsc::channel();\n 229|    thread::spawn(move || {\n 230|        let mut c = connect(&name);\n*231|        send(&mut c, KIND_SESSIONS, serde_json::Value::Null);\n 232|        loop {\n 233|            match read_frame(&mut c) {\n*234|                Ok(f) if f.kind == KIND_SESSIONS_REPLY => {\n 235|                    let r: SessionsReply = serde_json::from_value(f.payload).unwrap();\n 236|                    let _ = tx.send(r);\n 237|                    return;\n...\n 248|#[test]\n*249|fn blackholed_controller_lifecycle_five_invariants() {\n 250|    init_home();\n 251|    // Shrink the brain-write deadline so the wedged writer is \"stalled\" in ~400 ms\n 252|    // instead of 15 s — the structural outcome, fast + deterministic (the knob\n...\n 262|\n*263|    // ── UNRELATED session U (invariant 1): a second, independent flood child on\n 264|    //    its OWN endpoint. Drop its spawner conn (so U has no controller to wedge\n 265|    //    — the PTY keeps producing headless), and attach a SEPARATE DRAINING\n 266|    //    viewer whose frame count must keep advancing ACROSS the whole incident. ──\n*267|    let (u_spawn, sid_u) = spawn_session(&name, flood_spawn_req(\"blackhole-unrelated-ep\"));\n 268|    drop(u_spawn);\n 269|    let u_name = name.clone();\n 270|    let u_frames = Arc::new(AtomicU64::new(0));\n...\n 293|    //    DROP A so the ONLY persistent brain conn is the black-holed controller R. ──\n*294|    let (a, sid_b) = spawn_session(&name, seed_burst_spawn_req(\"blackhole-victim-ep\"));\n 295|    drop(a);\n 296|\n 297|    // ── Remote controller R: subscribes Control from_seq 0, drains until it has\n...\n 435|    // ── Observe exactly ONE logical stall eviction via an INDEPENDENT DRAINING\n*436|    //    control-plane connection (the KIND_SESSIONS reap runs the 400 ms\n 437|    //    stall-evict). Record t_release + freeze the resume cursor for the sid_b\n 438|    //    row. Accept a tally of {1, 2}: POISON-BEATS-EVICT (F-039 rider,\n 439|    //    doyle-ruled) — the physical watchdog can poison first and record the same\n...\n 445|    while Instant::now() < step6_deadline {\n*446|        let sessions = query_sessions(&name, Duration::from_secs(3));\n 447|        let count = stall_evict_count(&name, Duration::from_secs(3));\n 448|        if count >= 1 {\n 449|            stall_evicts_seen = count;\n## control_stamp_lifetime.rs\n 9|//!\n*10|//! `/exit` ends the RC session INDIRECTLY: the endpoint suspends when the child\n 11|//! binary terminates, so the teardown runs the REAP path (exit-waiter →\n 12|//! `sessions.remove`), NOT the controller-detach path (`detach_if` →\n 13|//! `clear_controller` → `stamp_driven_by`, the ONLY pre-fix stamp-clear). The reap\n...\n 24|//! writer; the session is dead so no live controller re-stamps concurrently (the\n*25|//! same race-free argument as the W5 no-session self-heal).\n 26|//!\n 27|//! ## Why kill the CHILD (not drop the conn)\n 28|//!\n...\n 99|\n*100|/// A long-lived stdin-reading PTY child so the spawned session stays LIVE until we\n 101|/// kill it (cross-OS).\n 102|fn idle_spawn_req(endpoint: &str) -> SpawnReq {\n 103|    #[cfg(unix)]\n...\n 140|\n*141|    // ── A REAL broker hosting a LIVE session for THIS endpoint. dispatch_spawn\n 142|    //    pre-attaches the spawner as the LOCAL controller (by=None) and its\n 143|    //    become_controller → stamp_driven_by stamps `controlled=true` on the perch. ──\n 144|    let name = unique_name();\n...\n 147|    let sid = spawner\n*148|        .spawn_session(idle_spawn_req(&id))\n*149|        .expect(\"spawn live session for the endpoint\");\n 150|\n 151|    // Give the spawn's become_controller stamp a beat to land.\n 152|    thread::sleep(Duration::from_millis(200));\n 153|\n*154|    // Precondition (genuine): the live session stamped `controlled=true` (the local\n 155|    // controller). This is a REAL broker stamp, not a hand-set value.\n 156|    let before = spt_store::info::read_info(&self_perch).expect(\"info present\");\n 157|    assert!(\n 158|        before.controlled,\n*159|        \"precondition: the live hosted session must stamp controlled=true \\\n 160|         (become_controller → stamp_driven_by) — got {before:?}\"\n 161|    );\n 162|\n*163|    // Simulate the FULL latch a remote-driven, multi-viewer session leaves in\n 164|    // info.json: driven_by=Some(remote) + viewer_count=2 (broker is the single\n 165|    // writer of both; here we set the on-disk state the reap must unlatch). The\n 166|    // spawner stays attached, idle → nothing re-stamps these before the reap.\n...\n 217|        after.driven_by, None,\n*218|        \"reap must clear driven_by (a remote-controller latch must not outlive the session)\"\n 219|    );\n 220|    assert!(\n 221|        !after.controlled,\n...\n 234|/// uncontrolled forever while driven (hall-b / the original ball-b report). The\n*235|/// `KIND_SESSIONS` poll now CONVERGES the perch to the live session truth,\n 236|/// divergence-gated. RED-first: without the convergence hook, the perch stays\n 237|/// `controlled:false` forever.\n 238|#[test]\n*239|fn converge_stamps_on_sessions_poll_after_late_bind() {\n 240|    init_stamp_home();\n 241|    let id = format!(\"ctlconv-{}\", std::process::id());\n 242|\n*243|    // A REAL broker + a spawned session for the endpoint — but NO perch yet, so the\n 244|    // spawn's become_controller → set_controlled(true) is SWALLOWED (NotFound): the\n 245|    // picker-created stamp-before-bind loss.\n 246|    let name = unique_name();\n...\n 249|    let sid = spawner\n*250|        .spawn_session(idle_spawn_req(&id))\n*251|        .expect(\"spawn live session before its perch exists\");\n 252|    thread::sleep(Duration::from_millis(150));\n 253|\n 254|    // The adapter binds LATE, writing controlled:false (InfoJson::new default) — the\n...\n 262|\n*263|    // A KIND_SESSIONS poll (what the daemon reconcile does each cadence) converges the\n 264|    // perch to broker truth: the spawner is the (local) controller ⇒ controlled=true.\n 265|    let mut converged = false;\n*266|    for _ in 0..50 {\n 267|        let mut brain = connect_brain(&name);\n*268|        let _ = brain.sessions().expect(\"KIND_SESSIONS reply\");\n 269|        thread::sleep(Duration::from_millis(50));\n 270|        if spt_store::info::read_info(&self_perch)\n 271|            .map(|i| i.controlled)\n...\n 287|        converged,\n*288|        \"the KIND_SESSIONS poll must converge controlled=true within the window — got {after:?}\"\n 289|    );\n 290|    assert!(\n 291|        after.controlled,\n*292|        \"controlled converged to the live-session truth (a local controller drives it)\"\n 293|    );\n 294|    assert_eq!(\n 295|        after.driven_by, None,\n## daemon_e2e.rs\n 8|//! 1. **spawn** — a [`Broker`] hosts a real PTY child; a [`Brain`] cold-starts,\n*9|//!    spawns the session, and round-trips a marker through it (the session is live).\n 10|//! 2. **Psyche-loop** — a [`BrainLifecycle`] (the consolidated re-host of the\n 11|//!    interim `api listen` live half, [`REQ-DAEMON-1`]) spawns the Psyche companion;\n 12|//!    the daemon marks the hosted perch `online`, so its liveness is the daemon\n...\n 157|    let mut brain = cold_retry(&broker_name, now);\n*158|    let sid = brain.spawn_session(echo_req()).expect(\"spawn echo child\");\n 159|    let child_pid = broker.session_pid(sid);\n 160|    assert_eq!(brain.generation(), 0, \"cold start is generation 0\");\n 161|\n...\n 249|            1,\n*250|            \"exactly one session across restarts\"\n 251|        );\n 252|        assert_eq!(\n 253|            broker.session_pid(sid),\n...\n 310|\n*311|    // ── Reap: kill the child and confirm the broker drops the session ───────────\n 312|    now += 1;\n 313|    let mut brain = handoff_retry(&broker_name, state, now);\n*314|    brain.kill_session().expect(\"kill child\");\n 315|    drop(brain);\n 316|    let mut reaped = false;\n 317|    for _ in 0..200 {\n## digest.rs\n 66|    std::fs::create_dir_all(&perch_path).unwrap();\n*67|    let mut rec = InfoJson::new(id, \"0\", std::process::id(), \"cc-session\", \"live_agent\");\n 68|    rec.adapter = Some(adapter);\n 69|    write_info(&perch_path, &rec).unwrap();\n 70|}\n...\n 104|    // ── Snapshot pull: projected on demand from a harness log, no broker/PTY ──\n*105|    // A fixture JSONL of published digest-records — the harness's \"session log\".\n*106|    let fixture = home.path().join(\"cc-session.jsonl\");\n 107|    std::fs::write(\n 108|        &fixture,\n 109|        \"{\\\"role\\\":\\\"input\\\",\\\"text\\\":\\\"add a file\\\"}\\n\\\n...\n 131|    // ── Delta-stream: a reproject after the log grows delivers a delta ───────\n*132|    let fixture2 = home.path().join(\"cc2-session.jsonl\");\n 133|    std::fs::write(&fixture2, \"{\\\"role\\\":\\\"input\\\",\\\"text\\\":\\\"first\\\"}\\n\").unwrap();\n 134|    establish_cc_endpoint(\"cc2\", &fixture2);\n 135|\n## dispatch.rs\n 323|    let mut found = false;\n*324|    for _ in 0..400 {\n 325|        let rows = store_b.list(\"d91-notif\").expect(\"list\");\n 326|        if rows\n 327|            .iter()\n...\n 397|    let mut spooled = false;\n*398|    for _ in 0..400 {\n 399|        let pending = spool::peek_all_at(&perch_path).expect(\"peek\");\n 400|        if pending\n 401|            .iter()\n...\n 416|// [int->REQ-INST-8] [int->REQ-REACH-1] remote-drive is served end to end by\n*417|// the dispatcher: B hosts a LABELED session; A attaches by session id, types,\n 418|// and reads the echo — the gate subject came from the broker's table (the\n*419|// spawn label), not wire bytes. An UNLABELED session refuses, fail-closed.\n 420|#[test]\n 421|fn dispatcher_serves_a_remote_drive_attach_undriven() {\n 422|    init_home();\n...\n 429|\n*430|    // B hosts one labeled echo session and one unlabeled one.\n 431|    #[cfg(unix)]\n 432|    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n 433|    #[cfg(windows)]\n...\n 436|    let labeled = b_host\n*437|        .spawn_session(spt_daemon::msg::SpawnReq {\n 438|            program: program.clone(),\n 439|            args: args.clone(),\n 440|            rows: 24,\n...\n 450|    let unlabeled = b_host\n*451|        .spawn_session(spt_daemon::msg::SpawnReq {\n 452|            program,\n 453|            args,\n 454|            rows: 24,\n...\n 469|\n*470|    // Unlabeled session: the dispatcher refuses (our read side finishes\n 471|    // without a single Output record).\n 472|    let refused = spt_daemon::attach::request_attach(&mut a, conn.conn_id, unlabeled, 0, MintedOp::new(Minter::Rc, op()), spt_net::net::attach::AttachIntent::Control)\n 473|        .expect(\"open refused viewport\");\n...\n 484|            } if stream_id == refused && !bytes.is_empty() => {\n*485|                panic!(\"an unlabeled session must never serve output\");\n 486|            }\n 487|            _ => {}\n 488|        }\n...\n 525|// [int->REQ-RC-CROSS-NODE-ATTACH] the cross-node leg's SERVER half: an operator\n*526|// that cannot know B's local session id attaches BY ENDPOINT (session_id = 0\n*527|// placeholder + endpoint_id), and B resolves endpoint→its own session id\n 528|// server-side (it is authoritative for its table) — the echo returns, proving\n*529|// the attach landed on the right session without A ever naming a session id.\n*530|// A named endpoint with NO live session on B (the D6 stale-registry-row case)\n 531|// refuses cleanly: the stream finishes with no output, never a false attach.\n 532|#[test]\n 533|fn dispatcher_resolves_attach_by_endpoint_and_refuses_a_stale_endpoint() {\n...\n 541|\n*542|    // B hosts a live labeled echo session — but A is NOT told its session id.\n 543|    #[cfg(unix)]\n 544|    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n 545|    #[cfg(windows)]\n...\n 548|    let _session = b_host\n*549|        .spawn_session(spt_daemon::msg::SpawnReq {\n 550|            program,\n 551|            args,\n 552|            rows: 24,\n...\n 567|\n*568|    // Attach BY ENDPOINT (session_id = 0 placeholder). B resolves it server-side.\n 569|    let viewport = spt_daemon::attach::request_attach_endpoint(\n 570|        &mut a,\n 571|        conn.conn_id,\n...\n 612|        String::from_utf8_lossy(&echoed).contains(\"by-endpoint\"),\n*613|        \"B resolved endpoint→session server-side and served the viewport; got {:?}\",\n 614|        String::from_utf8_lossy(&echoed)\n 615|    );\n 616|    // Detach the viewport cleanly before the refuse probe.\n...\n 618|\n*619|    // A STALE endpoint (no live session on B) refuses: clean finish, no output.\n 620|    let ghost = spt_daemon::attach::request_attach_endpoint(\n 621|        &mut a,\n 622|        conn.conn_id,\n...\n 646|                } if stream_id == ghost && !bytes.is_empty() => {\n*647|                    panic!(\"a stale endpoint with no live session must never serve output\");\n 648|                }\n 649|                _ => {}\n 650|            }\n...\n 654|        refused,\n*655|        \"a stale endpoint with no live session refuses with a clean stream finish\"\n 656|    );\n 657|    stop.store(true, Ordering::Relaxed);\n 658|}\n## driven_by_selfheal.rs\n 16|//!\n*17|//! - **Gap B (no session) — SHIPPED, GREEN.** The broker hosts NO session for the\n 18|//!   endpoint, but `info.json` is `status=online`, `controllable=Some(true)`,\n 19|//!   `driven_by=Some(node)`. `reconcile_hosted_liveness` marks the perch OFFLINE\n 20|//!   (`mark_offline` → `set_status(offline)`) AND clears `driven_by` to `None`\n 21|//!   (the `[impl->REQ-HAZARD-DRIVEN-BY-SELFHEAL]` block in `livehost.rs`, logging\n*22|//!   `DRIVEN_BY_SELFHEAL_OFFLINE`). RACE-FREE: with no live broker session there is\n 23|//!   no controller to re-stamp it concurrently. The Gap-B test below GREEN-locks\n 24|//!   this. (The original plan expected RED; the fix was already landed on this\n 25|//!   delivery branch — the test pins the GREEN behavior, not the buggy latch.)\n...\n 28|//!   The Gap-A test is NOT a pending RED bug-to-fix. It proves an INVARIANT: the\n*29|//!   reconcile MUST NOT clear `driven_by` for a perch that HAS a live session off a\n 30|//!   `controller_by==None` read, because that read is AMBIGUOUS — a live, LOCAL\n 31|//!   controller is pre-attached as `by=None` (`dispatch_spawn` →\n 32|//!   `become_controller(by=None)`), so `controller_by==None` cannot distinguish a\n 33|//!   lost controller from a live local one. A reconcile clear on `None` alone would\n*34|//!   FALSE-CLEAR a genuinely-driven session. The genuine production residual —\n*35|//!   an idle session whose ABANDONED REMOTE controller wedged without a clean\n 36|//!   detach — keeps `controller_by==Some(origin)`, which a reconcile read can't\n 37|//!   detect at all. That case is its own deferred requirement,\n 38|//!   `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT` (broker-side controller eviction —\n...\n 43|//!\n*44|//! The broker's `KIND_SESSIONS` reply carries `SessionInfo.controller_by`\n 45|//! (additive, `#[serde(default)]`). The Gap-A test REPORTS, for the live session,\n 46|//! the exact `controller_by` value the real broker returns, and surfaces the\n*47|//! CRITICAL finding (see the banner + the assertions): a locally-spawned session is\n 48|//! pre-attached as the LOCAL controller with `by=None`, so a LIVE, locally-driven\n 49|//! session ALSO reports `controller_by==None`. `controller_by==None` therefore is\n 50|//! UNUSABLE as a Gap-A clear signal — proving why the reconcile must leave the\n...\n 121|/// Connect a Brain to the broker with bounded retry (cold_start — the request-reply\n*122|/// carrier `Brain::sessions()` rides; KIND_SESSIONS is a single reply, no streaming,\n 123|/// so a plain Whole conn is fine here).\n 124|fn connect_brain(name: &str) -> Brain {\n 125|    for _ in 0..300 {\n...\n 133|\n*134|/// A trivial long-lived PTY child so a spawned session stays LIVE (Gap A needs a\n 135|/// real live session in the broker table). Cross-OS: a stdin-reading idle process.\n 136|fn idle_spawn_req(endpoint: &str) -> SpawnReq {\n 137|    #[cfg(unix)]\n...\n 172|#[test]\n*173|fn gap_b_sessionless_perch_reconcile_offlines_and_clears_driven_by() {\n 174|    init_selfheal_home();\n 175|    let node = \"PEERNODEHEX_GAP_B\";\n 176|    // A unique endpoint id per test so the process-global owlery never collides.\n...\n 186|\n*187|    // ── A REAL broker that hosts NO session for this endpoint (Gap B = the\n*188|    //    harness is gone). Query its KIND_SESSIONS reply over the real IPC and\n 189|    //    confirm the endpoint is ABSENT (the broker really hosts nothing for it). ──\n 190|    let name = unique_name();\n 191|    let _broker = start_broker(&name);\n 192|    let mut brain = connect_brain(&name);\n*193|    let reply = brain.sessions().expect(\"KIND_SESSIONS reply\");\n 194|    let live_sessions: BTreeSet<String> =\n 195|        reply.sessions.iter().map(|s| s.endpoint.clone()).collect();\n 196|\n*197|    // OBSERVATION (item 2): the endpoint is absent from the broker's session set.\n*198|    let endpoint_absent = !live_sessions.contains(&id);\n 199|    eprintln!(\n*200|        \"=== W5 GAP-B OBSERVATION: endpoint_in_sessions={} (expect ABSENT=true) sessions={:?} ===\",\n 201|        live_sessions.contains(&id),\n 202|        live_sessions\n 203|    );\n...\n 205|        endpoint_absent,\n*206|        \"Gap-B precondition: the broker must host NO session for the endpoint \\\n*207|         (it is absent from KIND_SESSIONS) — sessions={live_sessions:?}\"\n 208|    );\n 209|\n 210|    // ── THE RECONCILE TICK: call the CURRENT reconcile_hosted_liveness directly\n*211|    //    with the broker's real (empty-for-this-endpoint) live-session set. ──\n*212|    let offlined = reconcile_hosted_liveness(&owlery_dir(), &live_sessions);\n 213|\n 214|    let info_after = spt_store::info::read_info(&self_perch).expect(\"info present after reconcile\");\n 215|    let status_after = info_after.status.clone();\n## endpoint_survival.rs\n 10|//! live-host start and RE-RUNS the harness of every previously-ONLINE spt-hosted\n*11|//! endpoint the restart orphaned, resuming from its last ledger session.\n 12|//!\n 13|//! ## What this rig proves\n 14|//!\n*15|//! A fresh broker (an empty session table — exactly the state after a `daemon stop`)\n 16|//! plus a seeded previously-ONLINE, controllable (spt-hosted), relay-less, dead-custody\n 17|//! endpoint with a ledger row → the pass RE-SPAWNS its harness into a live broker PTY\n*18|//! session. RED-first: WITHOUT the pass the broker has no session for it (it stays\n 19|//! offline). The two belts are unit-proven in `livehost::restart_resume_gate_*`; this\n 20|//! rig proves the end-to-end re-run against the real broker + a real spawned harness.\n 21|//!\n...\n 33|\n*34|/// A long-lived, quiet harness the `[session.self]` resume spawns — it stays running\n 35|/// (so the broker session persists for the assertion) and produces no output.\n 36|#[cfg(windows)]\n 37|const HARNESS_CMD: &str = \"cmd /c ping -n 600 127.0.0.1 >nul\";\n...\n 41|/// Register a live-capable adapter whose `[session.self]` (the is_resume fallback when\n*42|/// no `[session.resume]`) spawns the long-lived quiet harness, so\n 43|/// `launch_harness_brokered_in` has a real program to run.\n 44|fn register_resume_adapter(name: &str) {\n 45|    let toml = format!(\n...\n 72|    // The ledger row the resume restores (session + adapter + cwd).\n*73|    let entry = spt_store::sessions::SessionEntry {\n 74|        ts: \"0\".to_string(),\n*75|        session_id: \"prev-session\".to_string(),\n*76|        trigger: spt_store::sessions::SessionTrigger::Boot,\n 77|        cwd: None,\n 78|        ordinal: None,\n 79|        adapter: Some(adapter.to_string()),\n...\n 90|\n*91|    // A FRESH broker at the well-known name — the empty-session state right after a\n*92|    // `daemon stop`+`start`. The pass's session-query + harness-launch both target it.\n 93|    let name = spt_daemon::broker_socket_name();\n 94|    let broker = Broker::bind(&name).expect(\"bind broker at the well-known name\");\n 95|    let serve = Arc::clone(&broker);\n...\n 110|\n*111|    // Precondition: the fresh broker hosts NO session for the endpoint (RED state).\n*112|    let sessions_before = live_session_ids(&name);\n 113|    assert!(\n 114|        !sessions_before.contains(&\"survivor\".to_string()),\n*115|        \"precondition: the fresh broker has no session for the orphaned endpoint\"\n 116|    );\n 117|\n 118|    // The pass under test — the one-shot daemon-restart survival re-run.\n...\n 123|\n*124|    // The endpoint's harness was re-run → a live broker session now exists for it.\n 125|    let mut appeared = false;\n 126|    let deadline = Instant::now() + Duration::from_secs(10);\n 127|    while Instant::now() < deadline {\n...\n 144|        \"the daemon-restart survival pass must re-run the orphaned endpoint's harness \\\n*145|         (a live broker session appears for it). RED without the pass: no session.\"\n 146|    );\n 147|}\n 148|\n*149|/// The endpoint ids the broker currently hosts a PTY session for (via a probe brain).\n*150|fn live_session_ids(name: &str) -> Vec<String> {\n 151|    let mut brain = match Brain::cold_start(name, now_ms()) {\n 152|        Ok(b) => b,\n 153|        Err(_) => return Vec::new(),\n...\n 161|/// The broker session id hosting the \"survivor\" endpoint, if any (for teardown).\n*162|fn survivor_session_id(name: &str) -> Option<u64> {\n 163|    let mut brain = Brain::cold_start(name, now_ms()).ok()?;\n 164|    brain\n 165|        .sessions()\n## false_promote.rs\n 260|        if f.kind == KIND_SPAWNED {\n*261|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 262|        }\n 263|    };\n 264|    // Inject the statically-held wedge (doyle Option C): a LOCAL controller whose\n...\n 271|        broker.test_seed_wedged_local_controller(sid, 1_000),\n*272|        \"precondition: the wedge seed must install on the hosted session\"\n 273|    );\n 274|    drop(a);\n 275|\n## handoff.rs\n 11|//! then (c) \"crashes\" by dropping its connection. Throughout, the child's pid is\n*12|//! unchanged and exactly one session is hosted; `gen_start` advances every\n 13|//! handoff and the generation counter increments.\n 14|//!\n 15|//! A real-process SIGKILL of the brain was proven in Spike #1; here the brain is\n...\n 95|    let sid = brain\n*96|        .spawn_session(echo_spawn_req())\n*97|        .expect(\"spawn session\");\n 98|    let child_pid = broker.session_pid(sid);\n 99|    assert_eq!(brain.generation(), 0);\n 100|\n...\n 137|            1,\n*138|            \"exactly one session across restarts\"\n 139|        );\n 140|        assert_eq!(\n 141|            broker.session_pid(sid),\n...\n 179|    let mut brain = handoff_retry(&name, state, now);\n*180|    brain.kill_session().expect(\"kill session\");\n 181|    loop {\n*182|        if let BrokerEvent::Exit { session_id, .. } = brain.read_event().expect(\"read until exit\") {\n 183|            assert_eq!(session_id, sid);\n 184|            break;\n 185|        }\n## idempotent.rs\n 167|    let sid = brain\n*168|        .spawn_session(echo_spawn_req())\n*169|        .expect(\"spawn session\");\n 170|    let child_pid = broker.session_pid(sid);\n 171|\n 172|    let mut buf: Vec<u8> = Vec::new();\n...\n 194|            1,\n*195|            \"exactly one session across restarts\"\n 196|        );\n 197|        assert_eq!(\n 198|            broker.session_pid(sid),\n## inject_control_wedge.rs\n 14|//! stalled and any path needing that log lock (a fresh attach, or the global\n*15|//! `KIND_SESSIONS` enumeration, which reads each log's `delivered_through`)\n 16|//! blocked FOREVER, while the broker process stayed alive. The repro that proved\n 17|//! this (captured 2026-06-19): `APPEND_CTRL_ENTER sid=1 seq=2` with no matching\n*18|//! `APPEND_CTRL_EXIT`, `broker_alive=true` but `sessions_answered=false`.\n 19|//!\n 20|//! POST-FIX (W1): controller delivery moved off the drain thread onto a dedicated\n 21|//! writer thread + bounded channel (the viewer_writer pattern, but authoritative:\n...\n 24|//! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed\n*25|//! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test\n 26|//! asserts that fixed behavior.\n 27|//!\n 28|//! ## RUNNER CONTRACT — run process-per-test (nextest) or single-threaded (W1b F1)\n...\n 50|    encode_bytes, EndpointInputReq, SpawnReq, Spawned, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT,\n*51|    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n 52|    KIND_SPAWNED,\n 53|};\n 54|use spt_daemon::transport::{send_hello, LocalSocketTransport};\n...\n 108|/// controller_by viewer, the keystroke op, AND the W2 result-gating helpers\n*109|/// (`output_contains_within` / `observe_with_xon` / `spawn_xlate_session`). NOT a\n 110|/// connect deadline: `cold_start_pump`'s socket connect is UNBOUNDED — the `Duration`\n 111|/// arg is stored as `io_timeout` and applied as `call_deadline = now + io_timeout` to\n 112|/// every bounded read (`read_event_until`), never to `LocalSocketTransport::connect`\n...\n\n[Showing lines 1-1044 of 1211 (50.0KB limit). Use :1045 to continue]"}],"details":{"scopePath":"crates/spt-daemon/tests, crates/spt/tests","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":250,"fileCount":20,"files":["crates/spt-daemon/tests/access.rs","crates/spt-daemon/tests/attach.rs","crates/spt-daemon/tests/brain_decouple.rs","crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","crates/spt-daemon/tests/brain_swap.rs","crates/spt-daemon/tests/broker.rs","crates/spt-daemon/tests/budget.rs","crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","crates/spt-daemon/tests/control_stamp_lifetime.rs","crates/spt-daemon/tests/daemon_e2e.rs","crates/spt-daemon/tests/digest.rs","crates/spt-daemon/tests/dispatch.rs","crates/spt-daemon/tests/driven_by_selfheal.rs","crates/spt-daemon/tests/endpoint_survival.rs","crates/spt-daemon/tests/false_promote.rs","crates/spt-daemon/tests/handoff.rs","crates/spt-daemon/tests/idempotent.rs","crates/spt-daemon/tests/inject_control_wedge.rs","crates/spt-daemon/tests/input_ack_deadlock.rs","crates/spt-daemon/tests/netbroker.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/access.rs","count":1},{"path":"crates/spt-daemon/tests/attach.rs","count":20},{"path":"crates/spt-daemon/tests/brain_decouple.rs","count":17},{"path":"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","count":20},{"path":"crates/spt-daemon/tests/brain_swap.rs","count":7},{"path":"crates/spt-daemon/tests/broker.rs","count":20},{"path":"crates/spt-daemon/tests/budget.rs","count":8},{"path":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","count":20},{"path":"crates/spt-daemon/tests/control_stamp_lifetime.rs","count":20},{"path":"crates/spt-daemon/tests/daemon_e2e.rs","count":5},{"path":"crates/spt-daemon/tests/digest.rs","count":4},{"path":"crates/spt-daemon/tests/dispatch.rs","count":20},{"path":"crates/spt-daemon/tests/driven_by_selfheal.rs","count":20},{"path":"crates/spt-daemon/tests/endpoint_survival.rs","count":18},{"path":"crates/spt-daemon/tests/false_promote.rs","count":2},{"path":"crates/spt-daemon/tests/handoff.rs","count":6},{"path":"crates/spt-daemon/tests/idempotent.rs","count":3},{"path":"crates/spt-daemon/tests/inject_control_wedge.rs","count":20},{"path":"crates/spt-daemon/tests/input_ack_deadlock.rs","count":15},{"path":"crates/spt-daemon/tests/netbroker.rs","count":4}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/tests/\n## access.rs\n 84│fn wait_for_stream(brain: &mut Brain) -> (u64, String) {\n*85│    for _ in 0..400 {\n 86│        let reply = brain.net_streams().expect(\"net-streams\");\n 87│        if let Some(s) = reply.streams.first() {\n 88│            return (s.stream_id, s.remote_id_hex.clone());\n## attach.rs\n   9│//! - **remote drive**: keystrokes sent from B arrive at A's PTY through the\n *10│//!   journaled input path, and the session's output streams back to B as\n  11│//!   sequenced records — B sees its own marker echoed by A's child;\n  12│//! - **origin is transport truth** (KNOWN-HAZARDS 7.5): the serve loop gates\n  13│//!   on the stream table's handshake-proven `remote_id_hex`;\n    │...\n 119│fn wait_for_stream(brain: &mut Brain) -> (u64, String) {\n*120│    for _ in 0..400 {\n 121│        let reply = brain.net_streams().expect(\"net-streams\");\n 122│        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 123│            return (s.stream_id, s.remote_id_hex.clone());\n    │...\n 137│/// Given an acquired `conn_id` on `operator` and the `target` broker connection\n*138│/// holding `session_id` for `endpoint`: open the attach stream, serve it on\n 139│/// `target` under `expected_origin` (asserted == the stream table's proven\n 140│/// origin), drive `marker` through the PTY and render it back over the wire,\n*141│/// then detach and confirm the serve loop ended `Detached` (session outlives).\n 142│/// Returns the rendered viewport bytes + the (still-live) target brain so the\n*143│/// caller can assert session survival and clean up.\n 144│#[allow(clippy::too_many_arguments)]\n 145│fn attach_drive_detach(\n 146│    operator: &mut Brain,\n    │...\n 154│) -> (Vec<u8>, Brain) {\n*155│    let stream_b = request_attach(operator, conn_id, session_id, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n 156│    operator\n 157│        .net_stream_subscribe(stream_b, 0)\n 158│        .expect(\"operator subscribe\");\n    │...\n 191│\n*192│    // Detach: the viewport ends with the session still running.\n 193│    operator\n 194│        .net_stream_send(stream_b, &[], None, true)\n 195│        .expect(\"detach\");\n    │...\n 294│// streams back. This is the W1 gate proof — \"bring up + attach a real harness\n*295│// endpoint\" over the byte-identical cross-node attach pump; the session outlives\n 296│// the viewport (detach never kills it).\n 297│#[test]\n 298│fn spt_hosted_bringup_then_cross_node_attach_drives_the_pty() {\n    │...\n 304│\n*305│    // Bring up an spt-hosted harness on A. Its `[session.self]` is a real echo\n 306│    // binary standing in for a harness — the bringup spawns it into broker A's\n 307│    // PTY exactly as it would a `claude` command.\n 308│    #[cfg(unix)]\n    │...\n 313│        \"[adapter]\\nname = \\\"mock\\\"\\nkind = \\\"harness\\\"\\nversion = \\\"1\\\"\\n\\\n*314│         min_spt_core_version = \\\"0\\\"\\n\\n[session.self]\\ncommand = '{self_cmd}'\\n\"\n 315│    ))\n 316│    .expect(\"harness manifest\");\n 317│    let spawned = spt_daemon::harnesshost::launch_harness_brokered_in(\n    │...\n 321│\n*322│    // Operator on B attaches CROSS-NODE to the brought-up session.\n 323│    let mut target = connect_retry(&name_a);\n 324│    let a_addr = target.net_status().expect(\"a status\").addr;\n 325│    let mut operator = connect_retry(&name_b);\n    │...\n 332│    let stream_b =\n*333│        request_attach(&mut operator, conn.conn_id, spawned.session_id, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n 334│    operator\n 335│        .net_stream_subscribe(stream_b, 0)\n 336│        .expect(\"operator subscribe\");\n    │...\n 369│\n*370│// [unit->REQ-INST-8] drive an agent('s session) on machine A from machine B:\n 371│// B's keystrokes reach A's real PTY child over the wire (journaled input\n 372│// path), the child's output streams back as sequenced records, and B renders\n 373│// its own marker echoed remotely. The serve gate sees the handshake-proven\n*374│// origin; B detaching ends the viewport with the session still running on A.\n 375│#[test]\n 376│fn remote_attach_drives_a_real_pty_cross_daemon() {\n 377│    init_home();\n    │...\n 385│    let sid = target\n*386│        .spawn_session(echo_spawn_req())\n 387│        .expect(\"spawn echo child\");\n 388│    let a_addr = target.net_status().expect(\"a status\").addr;\n 389│\n    │...\n 417│// [int->REQ-RC-1] LOCAL attach rides the byte-identical pump (M12 W1.5, B1):\n*418│// a single broker spawns a real PTY session; the operator acquires a\n 419│// broker-minted in-process LOOPBACK conn (`net_dial_loopback` — NOT a QUIC\n 420│// self-dial, which iroh refuses) and drives the PTY through the SAME\n 421│// `serve_attach`+render pump a cross-node operator uses. The proof that \"both\n    │...\n 426│// origin is THIS node's own id, gating same-node (access short-circuit Allow in\n*427│// production; default-open in this hermetic home — both Allow). The session\n 428│// outlives the viewport.\n 429│#[test]\n 430│fn local_attach_via_loopback_conn_rides_the_same_pump() {\n    │...\n 440│    let sid = target\n*441│        .spawn_session(echo_spawn_req())\n 442│        .expect(\"spawn echo child\");\n 443│\n 444│    // The operator acquires the broker's loopback conn; its `remote_id_hex` is\n    │...\n 468│// [unit->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT] the replay-vs-forward deadlock pin:\n*469│// when the session's OutputLog ring is ALREADY NON-EMPTY at attach (an\n*470│// already-producing session — exactly the endpoint-run shape, whose transient\n 471│// cold-start spawn conn drops after `Spawned`), `become_controller` replays MANY\n 472│// frames into the SERVING brain while `serve_attach` must forward each back to the\n 473│// operator. Before the two-conn split both rode ONE broker IPC connection, so the\n## brain_decouple.rs\n  10│//! `CONTROLLER_WRITE_DEADLINE`) fires only on NEW output, and `reap_dead_controller`\n *11│//! only caught an EXITED writer — so a controller BLOCKED on an idle session was\n  12│//! evicted by neither: control latched, reattach was refused, `rc --take` hung,\n  13│//! `daemon status` still read healthy.\n  14│//!\n    │...\n 247│        if f.kind == KIND_SPAWNED {\n*248│            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 249│        }\n 250│    };\n 251│\n 252│    // ── Remote controller R: a REMOTE operator (by=Some) takes control of the\n*253│    //    (locally-spawned, undriven) session, then STOPS reading — the suspended-\n 254│    //    brain analog. R MUST be a remote controller: a second remote's Control\n*255│    //    subscribe against a DIFFERENT-remote-driven session busy-refuses PRE-W2\n 256│    //    (the wedge), so only the stall-evict can free the slot — this is what\n 257│    //    isolates the fix from the pre-existing \"a remote takes an UNDRIVEN\n*258│    //    (local-only) session\" silent-control path. R's conn is held open (never\n 259│    //    read from) on its own thread so the broker's writer for R parks in\n 260│    //    write_frame exactly as against a suspended brain. ──\n 261│    let rname = name.clone();\n    │...\n 441│\n*442│/// One `KIND_SESSIONS` round-trip, returning the full reply. The dispatch runs\n 443│/// `reap_dead_controller` → the time-based `stall_evict_controller`, so polling\n 444│/// this both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 445│/// Bounded thread + channel (a wedge surfaces as a timeout, not a hang), like the\n    │...\n 448│#[cfg(windows)]\n*449│fn query_sessions(name: &str, deadline: Duration) -> Option<spt_daemon::msg::SessionsReply> {\n 450│    let name = name.to_string();\n 451│    let (tx, rx) = std::sync::mpsc::channel();\n 452│    thread::spawn(move || {\n 453│        let mut c = connect(&name);\n*454│        send(&mut c, spt_daemon::msg::KIND_SESSIONS, serde_json::Value::Null);\n 455│        loop {\n 456│            match read_frame(&mut c) {\n*457│                Ok(f) if f.kind == spt_daemon::msg::KIND_SESSIONS_REPLY => {\n*458│                    let r: spt_daemon::msg::SessionsReply =\n 459│                        serde_json::from_value(f.payload).unwrap();\n 460│                    let _ = tx.send(r);\n 461│                    return;\n    │...\n 496│        if f.kind == KIND_SPAWNED {\n*497│            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 498│        }\n 499│    };\n 500│    drop(a);\n    │...\n 634│    // ── Step 6: exactly ONE logical stall eviction, observed via an INDEPENDENT\n*635│    //    DRAINING control-plane connection (the KIND_SESSIONS reap runs the 400 ms\n 636│    //    stall-evict). Record t_release at first observation and freeze the resume\n 637│    //    cursor for the sid row. ──\n 638│    // POISON-BEATS-EVICT (F-039 rider, doyle-ruled 2026-07-10, 3rd CI trip): the\n    │...\n 651│    while Instant::now() < step6_deadline {\n*652│        let sessions = query_sessions(&name, Duration::from_secs(3));\n 653│        let count = stall_evict_count(&name, Duration::from_secs(3));\n 654│        if count >= 1 {\n 655│            stall_evicts_seen = count;\n 656│            t_release = Some(Instant::now());\n*657│            frozen_cursor = sessions\n 658│                .as_ref()\n*659│                .and_then(|r| r.sessions.iter().find(|s| s.session_id == sid))\n 660│                .map(|s| s.resume_seq)\n 661│                .or_else(|| {\n 662│                    query_sessions(&name, Duration::from_secs(3)).and_then(|r| {\n    │...\n 664│                            .iter()\n*665│                            .find(|s| s.session_id == sid)\n 666│                            .map(|s| s.resume_seq)\n 667│                    })\n 668│                });\n    │...\n 693│            s,\n*694│            &Envelope::new(spt_daemon::msg::KIND_SESSIONS, serde_json::Value::Null),\n 695│        )\n 696│    });\n 697│    let old_client_write_is_err = old_client_write.as_ref().map(|r| r.is_err());\n## brain_resume_conn_deadlock.rs\n   4│//! Proves the code-read root (docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md), DEAD-PEER\n  *5│//! INDEPENDENT: the daemon brain's `resume_sessions` subscribes broker PTY sessions\n   6│//! onto the brain's OWN request/reply conn. A conn's send half is one\n   7│//! `SharedSend = Arc<Mutex<SendHalf>>`; the subscriber writer threads\n   8│//! (`viewer_writer`/`controller_writer`) hold `send.lock()` ACROSS a BLOCKING\n   9│//! `write_frame`, and the dispatch reply path (`send_frame`) needs the SAME lock.\n *10│//! The daemon brain hosts no PTY sessions, so `run_brain` never drains that output —\n *11│//! it reads the conn only during its 500ms-heartbeat `net_status()`/`sessions()`\n *12│//! calls. When an actively-streaming session backs the conn up, the brain's own\n  13│//! subscriber writer blocks holding `send.lock()` → the heartbeat REPLY is starved →\n  14│//! the heartbeat stalls → the brain never drains → SELF-DEADLOCK. That is the field\n  15│//! wedge (both severities: respawn replay-burst + steady-state output backup), with\n    │...\n 112│\n*113│/// Spawn one flood PTY session; return (session_id, pid, spawner_conn). The conn is\n 114│/// kept alive (the spawner is the local by:None controller) but NOT drained — the\n 115│/// child floods the PTY regardless, so the broker's ring keeps advancing and the\n 116│/// brain's viewer subscription keeps receiving live output.\n    │...\n 127│            let s: Spawned = serde_json::from_value(f.payload).unwrap();\n*128│            return (s.session_id, s.pid.unwrap_or(0), c);\n 129│        }\n 130│    }\n 131│}\n    │...\n 133│/// How the probe brain resumes before running the heartbeat: the SHIPPING daemon\n*134│/// path — `CursorOnly` → `resume_session_cursors`, no subscribe. (The hazard path,\n 135│/// `resume_sessions`, is exercised directly by the deterministic zero-subscriber guard\n 136│/// `daemon_resume_leaves_zero_brain_subscribers` below, not through this probe.)\n 137│#[derive(Clone, Copy)]\n    │...\n 142│/// Drive `run_brain`'s heartbeat pattern on a fresh `Brain` conn: resume per `mode`,\n*143│/// then loop `net_status()` + `sessions()` — the exact per-tick IPC — incrementing a\n 144│/// shared counter per COMPLETED tick, so the caller can measure liveness in a window.\n 145│/// The probe NEVER drains subscribed output (it only issues request/reply, exactly like\n 146│/// the real non-draining daemon heartbeat) — so `CursorOnly` faithfully models the\n    │...\n 162│            ResumeMode::CursorOnly => {\n*163│                let _ = brain.resume_session_cursors();\n 164│            }\n 165│        }\n 166│        while !stop.load(Ordering::Relaxed) {\n    │...\n 170│            }\n*171│            if brain.sessions().is_err() {\n 172│                return;\n 173│            }\n 174│            ticks.fetch_add(1, Ordering::Relaxed);\n    │...\n 179│/// How many heartbeat ticks complete in `window` for a probe configured with `mode`.\n*180│/// N flood PTY sessions are already streaming before the probe starts.\n*181│fn heartbeat_ticks_under_flood(mode: ResumeMode, n_sessions: usize, window: Duration) -> u32 {\n 182│    init_home();\n 183│    let name = unique_name();\n 184│    let broker = Broker::bind(&name).expect(\"bind broker\");\n    │...\n 189│\n*190│    // Stand up N flooding PTY sessions the brain will resume.\n 191│    let mut conns = Vec::new();\n 192│    let mut pids = Vec::new();\n*193│    for i in 0..n_sessions {\n 194│        let (_sid, pid, conn) = spawn_flood(&name, &format!(\"flood-ep-{i}\"));\n 195│        pids.push(pid);\n 196│        conns.push(conn); // hold the spawner controller conns open (undrained)\n    │...\n 216│/// STEADY-STATE severity — the SHIPPING path: the daemon brain resumes CURSOR-ONLY\n*217│/// (`resume_session_cursors`, no subscribe) while N sessions actively stream, so its\n 218│/// heartbeat stays live. NO dead peer anywhere. RED-first: revert run_brain to the\n*219│/// subscribing `resume_sessions` (or see the regression-guard test below) and this\n 220│/// starves to a standstill.\n 221│// [int->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n 222│#[test]\n    │...\n 229│        ticks >= 100,\n*230│        \"the daemon's cursor-only resume must keep the heartbeat live under a {N}-session \\\n 231│         flood (got {ticks} ticks in {window:?}). A low count means the SharedSend \\\n 232│         self-deadlock re-appeared — the brain is subscribing a conn it does not drain.\"\n 233│    );\n    │...\n 236│/// RESPAWN-interleave severity — the SHIPPING path under the update-restart burst: a\n*237│/// fresh brain generation resumes CURSOR-ONLY while N sessions already flood at full\n 238│/// rate (the sessions predate this generation). The new generation's heartbeat — hence\n 239│/// its readiness/promotion — is never starved.\n 240│// [int->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n    │...\n 265│/// Two settled arms, no timing window:\n*266│///   POSITIVE — the shipping daemon path (`resume_session_cursors`) must leave EVERY\n 267│///   hosted session with ZERO viewers. A resume that instead subscribed the undrained\n 268│///   heartbeat conn re-opens the round-3 SharedSend self-deadlock class; post-r4 it\n 269│///   would instead get that heartbeat conn poisoned/retired by the write watchdog —\n    │...\n 272│///   takes the hazard path (`resume_sessions`, still present) and MUST raise every\n*273│///   session's viewer count to >= 1, proving `test_session_viewer_count` observes the\n 274│///   very subscription the positive arm forbids (a bare \"assert 0\" could pass vacuously\n 275│///   against an always-0 seam).\n 276│///\n## brain_swap.rs\n  15│//! produced while no brain was attached. Throughout, the child's pid is unchanged\n *16│//! and exactly one session is hosted: the endpoint never noticed the swap.\n  17│//!\n  18│//! **Filename note:** this file is `brain_swap.rs`, NOT `update.rs`, on purpose.\n  19│//! Windows' installer-detection heuristic (UAC) forces elevation for any\n    │...\n 109│    let sid = brain\n*110│        .spawn_session(echo_spawn_req())\n*111│        .expect(\"spawn session\");\n 112│    let child_pid = broker.session_pid(sid);\n 113│    assert_eq!(brain.generation(), 0);\n 114│\n    │...\n 147│        1,\n*148│        \"exactly one session across the update\"\n 149│    );\n 150│    assert_eq!(\n 151│        broker.session_pid(sid),\n    │...\n 173│    // The post-update brain still fully drives the same endpoint: kill it and see\n*174│    // the exit event + reaping — the session was live and owned, not a husk.\n*175│    brain.kill_session().expect(\"kill session\");\n 176│    loop {\n*177│        if let BrokerEvent::Exit { session_id, .. } = brain.read_event().expect(\"read until exit\") {\n 178│            assert_eq!(session_id, sid);\n 179│            break;\n 180│        }\n## broker.rs\n  144│        KIND_KILL,\n *145│        serde_json::to_value(KillReq { session_id: sid }).unwrap(),\n  146│    );\n  147│    let mut exit: Option<ExitEvent> = None;\n  148│    loop {\n     │...\n  160│    // Disconnect: dropping the single Stream closes the fd, so the broker's\n *161│    // command loop hits EOF and tears the session down. Joining the handler\n  162│    // (via the server thread) lets us assert the teardown happened.\n  163│    drop(conn);\n  164│    server.join().expect(\"server thread\");\n     │...\n  172│    assert_eq!(exit.session_id, sid);\n *173│    assert_eq!(broker.session_count(), 0, \"session torn down on disconnect\");\n  174│}\n  175│\n  176│fn accumulate_output(frame: &Envelope, out: &mut Vec<u8>) {\n     │...\n  187│/// inbound message addressed by ENDPOINT ID (`KIND_ENDPOINT_INPUT`) is delivered to\n *188│/// an spt-hosted session ONLY through its translation binary. With NO translation\n  189│/// binary declared (this echo child), idle delivery has no working binary → the\n  190│/// broker acks `delivered=false` and writes NOTHING to the PTY (the caller spools;\n  191│/// raw `payload+\\r` was removed as a delivery path — it did not submit on a modern\n     │...\n  235│\n *236│    // Endpoint-keyed inject (NO session_id — resolved by endpoint) into a hosted\n  237│    // session that declares NO translation binary: idle delivery is\n  238│    // translation-binary-ONLY, so there is no working binary → the broker acks\n  239│    // delivered=FALSE and writes NOTHING to the PTY (the caller spools).\n     │...\n  427│/// has EXITED surfaces a prompt Exit (dead-not-reaped → dispatch_subscribe's\n *428│/// try_wait → Exit frame) or a clean \"no such session\" error (already reaped) —\n  429│/// NEVER silence (the infinite-blank hang an attach would otherwise show). Spawn a\n  430│/// child that exits immediately, then poll-subscribe and assert a prompt\n  431│/// non-silent reply.\n  432│// [int->REQ-HAZARD-RC-ATTACH-FAILFAST]\n *433│fn dead_session_subscribe_test() {\n  434│    let name = unique_name();\n  435│    let broker = Broker::bind(&name).expect(\"bind broker\");\n  436│    let server_broker = Arc::clone(&broker);\n     │...\n  478│            Ok(f) if f.kind == KIND_SPAWNED => {\n *479│                sid = Some(serde_json::from_value::<Spawned>(f.payload).unwrap().session_id);\n  480│                break;\n  481│            }\n  482│            Ok(_) => {}\n     │...\n  487│\n *488│    // Poll-subscribe: every attempt must yield a prompt Exit or no-such-session\n  489│    // error — never an unanswered (silent) subscribe.\n  490│    let mut answered = false;\n  491│    for _ in 0..100 {\n     │...\n  525│#[test]\n *526│fn dead_session_subscribe_does_not_hang() {\n  527│    dead_session_subscribe_test();\n  528│}\n  529│\n     │...\n  578│        \"[adapter]\\nname = \\\"mock\\\"\\nkind = \\\"harness\\\"\\nversion = \\\"1\\\"\\n\\\n *579│         min_spt_core_version = \\\"0\\\"\\n\\n[session.self]\\ncommand = '{self_cmd}'\\n\\n\\\n  580│         [env.SPT_ENDPOINT_ID]\\ndirection = \\\"inject\\\"\\nvalue = \\\"{{id}}\\\"\\n\"\n  581│    );\n  582│    let manifest = spt_runtime::Manifest::from_toml_str(&manifest_toml).unwrap();\n     │...\n  617│    let mut saw_eid = false;\n *618│    for _ in 0..400 {\n  619│        if String::from_utf8_lossy(&out).contains(\"EID=wall-b\") {\n  620│            saw_eid = true;\n  621│            break;\n     │...\n  636│    // The send leg (ADR-0022 amendment / REQ-HAZARD-IDLE-SILENT-NONDELIVERY): an\n *637│    // endpoint-keyed inject to a session with NO translation binary SPOOLS — the\n  638│    // broker acks delivered=false and writes NOTHING to the PTY (raw inject removed;\n  639│    // a bare payload+\\r does not submit on a modern TUI). Read the\n  640│    // KIND_ENDPOINT_INJECTED ack on the SENDER conn — it DOES arrive — and assert\n     │...\n  707│\n *708│/// Spawn the long-lived echo child and return its session id (draining any\n  709│/// interleaved early output into `out`).\n  710│fn spawn_echo(conn: &mut Stream, out: &mut Vec<u8>) -> u64 {\n  711│    send(conn, KIND_SPAWN, serde_json::to_value(echo_spawn_req()).unwrap());\n     │...\n  759│/// REQ-HAZARD-INPUT-ACK-BACKPRESSURE — the ACK GATE, the crux unit. Against a\n *760│/// REAL broker hosting a REAL spawned PTY session, an `op_id`-carrying input with\n  761│/// `ack = false` produces NO `KIND_APPLIED` frame, while one with `ack = true`\n  762│/// does. This is the broker leg of the fix: `dispatch_input` writes the applied\n  763│/// envelope ONLY when `req.ack`, so the fire-and-forward operator/rc flood never\n     │...\n  810│        let mut probe_echoed = false;\n *811│        for _ in 0..4000 {\n  812│            if first_applied_op == Some(PROBE_OP) && noack_echoed && probe_echoed {\n  813│                break;\n  814│            }\n     │...\n  905│    // Drive the send + drain on a worker (the blocking `read_frame` can stall once\n *906│    // steady-state output stops) feeding back (session_id, echo_count). The main\n  907│    // thread bounds it with a watchdog, then reads the AUTHORITATIVE exactly-once\n  908│    // evidence straight off the broker-owned journal.\n  909│    let (tx, rx) = std::sync::mpsc::channel::<(u64, usize)>();\n     │...\n 1032│        if f.kind == KIND_SPAWNED {\n*1033│            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 1034│        }\n 1035│    };\n 1036│\n     │...\n 1132│    // Send a UNIQUE post-subscribe marker; reading the consumer stream until it is\n*1133│    // ACCEPTED both bounds the read AND proves the session stayed LIVE through the\n 1134│    // double controller swap (input still reaches the PTY, output still flows).\n 1135│    send(\n 1136│        &mut conn,\n     │...\n 1193│        KIND_KILL,\n*1194│        serde_json::to_value(KillReq { session_id: sid }).unwrap(),\n 1195│    );\n 1196│    loop {\n 1197│        match read_frame(&mut conn) {\n## budget.rs\n  11│//!\n *12│//! A **dormant** seat is a live, state-preserved harness session held warm\n  13│//! by the broker (PTY master + child process + output ring). A **suspended**\n  14│//! seat is that session closed — only the on-disk perch record remains. So:\n  15│//!\n *16│//! 1. spawn N idle sessions through the real broker (the production PTY\n  17│//!    hosting path), labeled per seat;\n  18│//! 2. settle, then sample each child's RSS / handle (fd) count / CPU time,\n  19│//!    twice over an idle window (the delta = idle CPU burn);\n    │...\n  79│\n *80│    // One probe brain per seat (kill_session targets the brain's own session).\n *81│    let mut seats: Vec<(Brain, u64, u32)> = Vec::new(); // (brain, session, child pid)\n  82│    for i in 0..n {\n  83│        let mut brain = connect_retry(&name);\n  84│        let req = SpawnReq {\n    │...\n  95│        };\n *96│        let (session, pid) = brain.spawn_session_pid(req).expect(\"spawn seat\");\n  97│        let pid = pid.expect(\"seat child pid\");\n *98│        seats.push((brain, session, pid));\n  99│    }\n 100│\n 101│    thread::sleep(settle);\n    │...\n 153│\n*154│    // Suspend: close every seat (the session ends; only the perch record\n 155│    // would remain in production). The residual is the daemon baseline.\n 156│    for (brain, _, _) in seats.iter_mut() {\n*157│        let _ = brain.kill_session();\n 158│    }\n 159│    thread::sleep(Duration::from_millis(1_500));\n 160│    let self_after = sample_pid(std::process::id()).unwrap_or_default();\n## conn_blackhole_lifecycle.rs\n  14│//! src/conn.rs` bounded/cancelable/poison-on-failure writes, landed fc048bb),\n *15│//! asserts hertz's FIVE invariants for ONE incident:\n  16│//!\n *17│//!   1. UNRELATED SESSIONS CONTINUE — a second, independent session's viewer keeps\n  18│//!      receiving output throughout the black-hole incident (the fan-out never\n  19│//!      blocks the whole broker on the one wedged conn);\n  20│//!   2. THE BAD PHYSICAL CONNECTION IS CANCELED/CLOSED WITHIN THE BOUND — a write\n    │...\n  34│//!      PROXIES (this invariant has no direct seam): after retirement the broker\n *35│//!      still answers a control-plane `KIND_SESSIONS` request PROMPTLY (no lock\n  36│//!      is held by the retired writer), `conn_handler_count()` returns to the\n  37│//!      unrelated-viewer floor without the client dropping its end (no task\n  38│//!      lingers), AND both the\n *39│//!      unrelated session's viewer and the fresh viewer flow output (the shared\n  40│//!      fan-out lock is free). The conjunction of these three is the strongest\n  41│//!      deterministic proxy available for \"nothing is still owned.\"\n  42│//!\n    │...\n  53│//! not have, which are exactly hertz's invariants (1) and (4): a SECOND\n *54│//! UNRELATED session whose viewer keeps ticking across the incident, and a fresh\n  55│//! VIEWER attach (not just a controller take) proven to receive output.\n  56│//!\n  57│//! ## Why Windows-mandatory (`#![cfg(windows)]` — the whole file)\n    │...\n  89│use spt_daemon::msg::{\n *90│    decode_bytes, encode_bytes, InputReq, OutputEvent, SessionsReply, SpawnReq, Spawned,\n  91│    StallEvictsReply, SubscribeOutcome, SubscribeReq, SubscribedReply, KIND_INPUT, KIND_OUTPUT,\n *92│    KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN, KIND_SPAWNED, KIND_STALL_EVICTS,\n  93│    KIND_STALL_EVICTS_REPLY, KIND_SUBSCRIBE, KIND_SUBSCRIBED,\n  94│};\n  95│use spt_daemon::transport::{send_hello, LocalSocketTransport};\n    │...\n 182│\n*183│/// Spawn a PTY child from a fresh conn and return `(spawn_conn, session_id)`.\n*184│fn spawn_session(name: &str, req: SpawnReq) -> (Stream, u64) {\n 185│    let mut c = connect(name);\n 186│    send(&mut c, KIND_SPAWN, serde_json::to_value(req).unwrap());\n 187│    let sid = loop {\n    │...\n 189│        if f.kind == KIND_SPAWNED {\n*190│            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 191│        }\n 192│    };\n 193│    (c, sid)\n    │...\n 221│\n*222│/// One `KIND_SESSIONS` round-trip, returning the reply. The dispatch runs the\n 223│/// `reap_dead_controller` → time-based `stall_evict_controller`, so polling this\n 224│/// both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 225│/// Bounded thread + channel. Verbatim sibling of the r4 gate helper.\n*226│fn query_sessions(name: &str, deadline: Duration) -> Option<SessionsReply> {\n 227│    let name = name.to_string();\n 228│    let (tx, rx) = std::sync::mpsc::channel();\n 229│    thread::spawn(move || {\n 230│        let mut c = connect(&name);\n*231│        send(&mut c, KIND_SESSIONS, serde_json::Value::Null);\n 232│        loop {\n 233│            match read_frame(&mut c) {\n*234│                Ok(f) if f.kind == KIND_SESSIONS_REPLY => {\n 235│                    let r: SessionsReply = serde_json::from_value(f.payload).unwrap();\n 236│                    let _ = tx.send(r);\n 237│                    return;\n    │...\n 248│#[test]\n*249│fn blackholed_controller_lifecycle_five_invariants() {\n 250│    init_home();\n 251│    // Shrink the brain-write deadline so the wedged writer is \"stalled\" in ~400 ms\n 252│    // instead of 15 s — the structural outcome, fast + deterministic (the knob\n    │...\n 262│\n*263│    // ── UNRELATED session U (invariant 1): a second, independent flood child on\n 264│    //    its OWN endpoint. Drop its spawner conn (so U has no controller to wedge\n 265│    //    — the PTY keeps producing headless), and attach a SEPARATE DRAINING\n 266│    //    viewer whose frame count must keep advancing ACROSS the whole incident. ──\n*267│    let (u_spawn, sid_u) = spawn_session(&name, flood_spawn_req(\"blackhole-unrelated-ep\"));\n 268│    drop(u_spawn);\n 269│    let u_name = name.clone();\n 270│    let u_frames = Arc::new(AtomicU64::new(0));\n    │...\n 293│    //    DROP A so the ONLY persistent brain conn is the black-holed controller R. ──\n*294│    let (a, sid_b) = spawn_session(&name, seed_burst_spawn_req(\"blackhole-victim-ep\"));\n 295│    drop(a);\n 296│\n 297│    // ── Remote controller R: subscribes Control from_seq 0, drains until it has\n    │...\n 435│    // ── Observe exactly ONE logical stall eviction via an INDEPENDENT DRAINING\n*436│    //    control-plane connection (the KIND_SESSIONS reap runs the 400 ms\n 437│    //    stall-evict). Record t_release + freeze the resume cursor for the sid_b\n 438│    //    row. Accept a tally of {1, 2}: POISON-BEATS-EVICT (F-039 rider,\n 439│    //    doyle-ruled) — the physical watchdog can poison first and record the same\n    │...\n 445│    while Instant::now() < step6_deadline {\n*446│        let sessions = query_sessions(&name, Duration::from_secs(3));\n 447│        let count = stall_evict_count(&name, Duration::from_secs(3));\n 448│        if count >= 1 {\n 449│            stall_evicts_seen = count;\n## control_stamp_lifetime.rs\n   9│//!\n *10│//! `/exit` ends the RC session INDIRECTLY: the endpoint suspends when the child\n  11│//! binary terminates, so the teardown runs the REAP path (exit-waiter →\n  12│//! `sessions.remove`), NOT the controller-detach path (`detach_if` →\n  13│//! `clear_controller` → `stamp_driven_by`, the ONLY pre-fix stamp-clear). The reap\n    │...\n  24│//! writer; the session is dead so no live controller re-stamps concurrently (the\n *25│//! same race-free argument as the W5 no-session self-heal).\n  26│//!\n  27│//! ## Why kill the CHILD (not drop the conn)\n  28│//!\n    │...\n  99│\n*100│/// A long-lived stdin-reading PTY child so the spawned session stays LIVE until we\n 101│/// kill it (cross-OS).\n 102│fn idle_spawn_req(endpoint: &str) -> SpawnReq {\n 103│    #[cfg(unix)]\n    │...\n 140│\n*141│    // ── A REAL broker hosting a LIVE session for THIS endpoint. dispatch_spawn\n 142│    //    pre-attaches the spawner as the LOCAL controller (by=None) and its\n 143│    //    become_controller → stamp_driven_by stamps `controlled=true` on the perch. ──\n 144│    let name = unique_name();\n    │...\n 147│    let sid = spawner\n*148│        .spawn_session(idle_spawn_req(&id))\n*149│        .expect(\"spawn live session for the endpoint\");\n 150│\n 151│    // Give the spawn's become_controller stamp a beat to land.\n 152│    thread::sleep(Duration::from_millis(200));\n 153│\n*154│    // Precondition (genuine): the live session stamped `controlled=true` (the local\n 155│    // controller). This is a REAL broker stamp, not a hand-set value.\n 156│    let before = spt_store::info::read_info(&self_perch).expect(\"info present\");\n 157│    assert!(\n 158│        before.controlled,\n*159│        \"precondition: the live hosted session must stamp controlled=true \\\n 160│         (become_controller → stamp_driven_by) — got {before:?}\"\n 161│    );\n 162│\n*163│    // Simulate the FULL latch a remote-driven, multi-viewer session leaves in\n 164│    // info.json: driven_by=Some(remote) + viewer_count=2 (broker is the single\n 165│    // writer of both; here we set the on-disk state the reap must unlatch). The\n 166│    // spawner stays attached, idle → nothing re-stamps these before the reap.\n    │...\n 217│        after.driven_by, None,\n*218│        \"reap must clear driven_by (a remote-controller latch must not outlive the session)\"\n 219│    );\n 220│    assert!(\n 221│        !after.controlled,\n    │...\n 234│/// uncontrolled forever while driven (hall-b / the original ball-b report). The\n*235│/// `KIND_SESSIONS` poll now CONVERGES the perch to the live session truth,\n 236│/// divergence-gated. RED-first: without the convergence hook, the perch stays\n 237│/// `controlled:false` forever.\n 238│#[test]\n*239│fn converge_stamps_on_sessions_poll_after_late_bind() {\n 240│    init_stamp_home();\n 241│    let id = format!(\"ctlconv-{}\", std::process::id());\n 242│\n*243│    // A REAL broker + a spawned session for the endpoint — but NO perch yet, so the\n 244│    // spawn's become_controller → set_controlled(true) is SWALLOWED (NotFound): the\n 245│    // picker-created stamp-before-bind loss.\n 246│    let name = unique_name();\n    │...\n 249│    let sid = spawner\n*250│        .spawn_session(idle_spawn_req(&id))\n*251│        .expect(\"spawn live session before its perch exists\");\n 252│    thread::sleep(Duration::from_millis(150));\n 253│\n 254│    // The adapter binds LATE, writing controlled:false (InfoJson::new default) — the\n    │...\n 262│\n*263│    // A KIND_SESSIONS poll (what the daemon reconcile does each cadence) converges the\n 264│    // perch to broker truth: the spawner is the (local) controller ⇒ controlled=true.\n 265│    let mut converged = false;\n*266│    for _ in 0..50 {\n 267│        let mut brain = connect_brain(&name);\n*268│        let _ = brain.sessions().expect(\"KIND_SESSIONS reply\");\n 269│        thread::sleep(Duration::from_millis(50));\n 270│        if spt_store::info::read_info(&self_perch)\n 271│            .map(|i| i.controlled)\n    │...\n 287│        converged,\n*288│        \"the KIND_SESSIONS poll must converge controlled=true within the window — got {after:?}\"\n 289│    );\n 290│    assert!(\n 291│        after.controlled,\n*292│        \"controlled converged to the live-session truth (a local controller drives it)\"\n 293│    );\n 294│    assert_eq!(\n 295│        after.driven_by, None,\n## daemon_e2e.rs\n   8│//! 1. **spawn** — a [`Broker`] hosts a real PTY child; a [`Brain`] cold-starts,\n  *9│//!    spawns the session, and round-trips a marker through it (the session is live).\n  10│//! 2. **Psyche-loop** — a [`BrainLifecycle`] (the consolidated re-host of the\n  11│//!    interim `api listen` live half, [`REQ-DAEMON-1`]) spawns the Psyche companion;\n  12│//!    the daemon marks the hosted perch `online`, so its liveness is the daemon\n    │...\n 157│    let mut brain = cold_retry(&broker_name, now);\n*158│    let sid = brain.spawn_session(echo_req()).expect(\"spawn echo child\");\n 159│    let child_pid = broker.session_pid(sid);\n 160│    assert_eq!(brain.generation(), 0, \"cold start is generation 0\");\n 161│\n    │...\n 249│            1,\n*250│            \"exactly one session across restarts\"\n 251│        );\n 252│        assert_eq!(\n 253│            broker.session_pid(sid),\n    │...\n 310│\n*311│    // ── Reap: kill the child and confirm the broker drops the session ───────────\n 312│    now += 1;\n 313│    let mut brain = handoff_retry(&broker_name, state, now);\n*314│    brain.kill_session().expect(\"kill child\");\n 315│    drop(brain);\n 316│    let mut reaped = false;\n 317│    for _ in 0..200 {\n## digest.rs\n  66│    std::fs::create_dir_all(&perch_path).unwrap();\n *67│    let mut rec = InfoJson::new(id, \"0\", std::process::id(), \"cc-session\", \"live_agent\");\n  68│    rec.adapter = Some(adapter);\n  69│    write_info(&perch_path, &rec).unwrap();\n  70│}\n    │...\n 104│    // ── Snapshot pull: projected on demand from a harness log, no broker/PTY ──\n*105│    // A fixture JSONL of published digest-records — the harness's \"session log\".\n*106│    let fixture = home.path().join(\"cc-session.jsonl\");\n 107│    std::fs::write(\n 108│        &fixture,\n 109│        \"{\\\"role\\\":\\\"input\\\",\\\"text\\\":\\\"add a file\\\"}\\n\\\n    │...\n 131│    // ── Delta-stream: a reproject after the log grows delivers a delta ───────\n*132│    let fixture2 = home.path().join(\"cc2-session.jsonl\");\n 133│    std::fs::write(&fixture2, \"{\\\"role\\\":\\\"input\\\",\\\"text\\\":\\\"first\\\"}\\n\").unwrap();\n 134│    establish_cc_endpoint(\"cc2\", &fixture2);\n 135│\n## dispatch.rs\n 323│    let mut found = false;\n*324│    for _ in 0..400 {\n 325│        let rows = store_b.list(\"d91-notif\").expect(\"list\");\n 326│        if rows\n 327│            .iter()\n    │...\n 397│    let mut spooled = false;\n*398│    for _ in 0..400 {\n 399│        let pending = spool::peek_all_at(&perch_path).expect(\"peek\");\n 400│        if pending\n 401│            .iter()\n    │...\n 416│// [int->REQ-INST-8] [int->REQ-REACH-1] remote-drive is served end to end by\n*417│// the dispatcher: B hosts a LABELED session; A attaches by session id, types,\n 418│// and reads the echo — the gate subject came from the broker's table (the\n*419│// spawn label), not wire bytes. An UNLABELED session refuses, fail-closed.\n 420│#[test]\n 421│fn dispatcher_serves_a_remote_drive_attach_undriven() {\n 422│    init_home();\n    │...\n 429│\n*430│    // B hosts one labeled echo session and one unlabeled one.\n 431│    #[cfg(unix)]\n 432│    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n 433│    #[cfg(windows)]\n    │...\n 436│    let labeled = b_host\n*437│        .spawn_session(spt_daemon::msg::SpawnReq {\n 438│            program: program.clone(),\n 439│            args: args.clone(),\n 440│            rows: 24,\n    │...\n 450│    let unlabeled = b_host\n*451│        .spawn_session(spt_daemon::msg::SpawnReq {\n 452│            program,\n 453│            args,\n 454│            rows: 24,\n    │...\n 469│\n*470│    // Unlabeled session: the dispatcher refuses (our read side finishes\n 471│    // without a single Output record).\n 472│    let refused = spt_daemon::attach::request_attach(&mut a, conn.conn_id, unlabeled, 0, MintedOp::new(Minter::Rc, op()), spt_net::net::attach::AttachIntent::Control)\n 473│        .expect(\"open refused viewport\");\n    │...\n 484│            } if stream_id == refused && !bytes.is_empty() => {\n*485│                panic!(\"an unlabeled session must never serve output\");\n 486│            }\n 487│            _ => {}\n 488│        }\n    │...\n 525│// [int->REQ-RC-CROSS-NODE-ATTACH] the cross-node leg's SERVER half: an operator\n*526│// that cannot know B's local session id attaches BY ENDPOINT (session_id = 0\n*527│// placeholder + endpoint_id), and B resolves endpoint→its own session id\n 528│// server-side (it is authoritative for its table) — the echo returns, proving\n*529│// the attach landed on the right session without A ever naming a session id.\n*530│// A named endpoint with NO live session on B (the D6 stale-registry-row case)\n 531│// refuses cleanly: the stream finishes with no output, never a false attach.\n 532│#[test]\n 533│fn dispatcher_resolves_attach_by_endpoint_and_refuses_a_stale_endpoint() {\n    │...\n 541│\n*542│    // B hosts a live labeled echo session — but A is NOT told its session id.\n 543│    #[cfg(unix)]\n 544│    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n 545│    #[cfg(windows)]\n    │...\n 548│    let _session = b_host\n*549│        .spawn_session(spt_daemon::msg::SpawnReq {\n 550│            program,\n 551│            args,\n 552│            rows: 24,\n    │...\n 567│\n*568│    // Attach BY ENDPOINT (session_id = 0 placeholder). B resolves it server-side.\n 569│    let viewport = spt_daemon::attach::request_attach_endpoint(\n 570│        &mut a,\n 571│        conn.conn_id,\n    │...\n 612│        String::from_utf8_lossy(&echoed).contains(\"by-endpoint\"),\n*613│        \"B resolved endpoint→session server-side and served the viewport; got {:?}\",\n 614│        String::from_utf8_lossy(&echoed)\n 615│    );\n 616│    // Detach the viewport cleanly before the refuse probe.\n    │...\n 618│\n*619│    // A STALE endpoint (no live session on B) refuses: clean finish, no output.\n 620│    let ghost = spt_daemon::attach::request_attach_endpoint(\n 621│        &mut a,\n 622│        conn.conn_id,\n    │...\n 646│                } if stream_id == ghost && !bytes.is_empty() => {\n*647│                    panic!(\"a stale endpoint with no live session must never serve output\");\n 648│                }\n 649│                _ => {}\n 650│            }\n    │...\n 654│        refused,\n*655│        \"a stale endpoint with no live session refuses with a clean stream finish\"\n 656│    );\n 657│    stop.store(true, Ordering::Relaxed);\n 658│}\n## driven_by_selfheal.rs\n  16│//!\n *17│//! - **Gap B (no session) — SHIPPED, GREEN.** The broker hosts NO session for the\n  18│//!   endpoint, but `info.json` is `status=online`, `controllable=Some(true)`,\n  19│//!   `driven_by=Some(node)`. `reconcile_hosted_liveness` marks the perch OFFLINE\n  20│//!   (`mark_offline` → `set_status(offline)`) AND clears `driven_by` to `None`\n  21│//!   (the `[impl->REQ-HAZARD-DRIVEN-BY-SELFHEAL]` block in `livehost.rs`, logging\n *22│//!   `DRIVEN_BY_SELFHEAL_OFFLINE`). RACE-FREE: with no live broker session there is\n  23│//!   no controller to re-stamp it concurrently. The Gap-B test below GREEN-locks\n  24│//!   this. (The original plan expected RED; the fix was already landed on this\n  25│//!   delivery branch — the test pins the GREEN behavior, not the buggy latch.)\n    │...\n  28│//!   The Gap-A test is NOT a pending RED bug-to-fix. It proves an INVARIANT: the\n *29│//!   reconcile MUST NOT clear `driven_by` for a perch that HAS a live session off a\n  30│//!   `controller_by==None` read, because that read is AMBIGUOUS — a live, LOCAL\n  31│//!   controller is pre-attached as `by=None` (`dispatch_spawn` →\n  32│//!   `become_controller(by=None)`), so `controller_by==None` cannot distinguish a\n  33│//!   lost controller from a live local one. A reconcile clear on `None` alone would\n *34│//!   FALSE-CLEAR a genuinely-driven session. The genuine production residual —\n *35│//!   an idle session whose ABANDONED REMOTE controller wedged without a clean\n  36│//!   detach — keeps `controller_by==Some(origin)`, which a reconcile read can't\n  37│//!   detect at all. That case is its own deferred requirement,\n  38│//!   `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT` (broker-side controller eviction —\n    │...\n  43│//!\n *44│//! The broker's `KIND_SESSIONS` reply carries `SessionInfo.controller_by`\n  45│//! (additive, `#[serde(default)]`). The Gap-A test REPORTS, for the live session,\n  46│//! the exact `controller_by` value the real broker returns, and surfaces the\n *47│//! CRITICAL finding (see the banner + the assertions): a locally-spawned session is\n  48│//! pre-attached as the LOCAL controller with `by=None`, so a LIVE, locally-driven\n  49│//! session ALSO reports `controller_by==None`. `controller_by==None` therefore is\n  50│//! UNUSABLE as a Gap-A clear signal — proving why the reconcile must leave the\n    │...\n 121│/// Connect a Brain to the broker with bounded retry (cold_start — the request-reply\n*122│/// carrier `Brain::sessions()` rides; KIND_SESSIONS is a single reply, no streaming,\n 123│/// so a plain Whole conn is fine here).\n 124│fn connect_brain(name: &str) -> Brain {\n 125│    for _ in 0..300 {\n    │...\n 133│\n*134│/// A trivial long-lived PTY child so a spawned session stays LIVE (Gap A needs a\n 135│/// real live session in the broker table). Cross-OS: a stdin-reading idle process.\n 136│fn idle_spawn_req(endpoint: &str) -> SpawnReq {\n 137│    #[cfg(unix)]\n    │...\n 172│#[test]\n*173│fn gap_b_sessionless_perch_reconcile_offlines_and_clears_driven_by() {\n 174│    init_selfheal_home();\n 175│    let node = \"PEERNODEHEX_GAP_B\";\n 176│    // A unique endpoint id per test so the process-global owlery never collides.\n    │...\n 186│\n*187│    // ── A REAL broker that hosts NO session for this endpoint (Gap B = the\n*188│    //    harness is gone). Query its KIND_SESSIONS reply over the real IPC and\n 189│    //    confirm the endpoint is ABSENT (the broker really hosts nothing for it). ──\n 190│    let name = unique_name();\n 191│    let _broker = start_broker(&name);\n 192│    let mut brain = connect_brain(&name);\n*193│    let reply = brain.sessions().expect(\"KIND_SESSIONS reply\");\n 194│    let live_sessions: BTreeSet<String> =\n 195│        reply.sessions.iter().map(|s| s.endpoint.clone()).collect();\n 196│\n*197│    // OBSERVATION (item 2): the endpoint is absent from the broker's session set.\n*198│    let endpoint_absent = !live_sessions.contains(&id);\n 199│    eprintln!(\n*200│        \"=== W5 GAP-B OBSERVATION: endpoint_in_sessions={} (expect ABSENT=true) sessions={:?} ===\",\n 201│        live_sessions.contains(&id),\n 202│        live_sessions\n 203│    );\n    │...\n 205│        endpoint_absent,\n*206│        \"Gap-B precondition: the broker must host NO session for the endpoint \\\n*207│         (it is absent from KIND_SESSIONS) — sessions={live_sessions:?}\"\n 208│    );\n 209│\n 210│    // ── THE RECONCILE TICK: call the CURRENT reconcile_hosted_liveness directly\n*211│    //    with the broker's real (empty-for-this-endpoint) live-session set. ──\n*212│    let offlined = reconcile_hosted_liveness(&owlery_dir(), &live_sessions);\n 213│\n 214│    let info_after = spt_store::info::read_info(&self_perch).expect(\"info present after reconcile\");\n 215│    let status_after = info_after.status.clone();\n## endpoint_survival.rs\n  10│//! live-host start and RE-RUNS the harness of every previously-ONLINE spt-hosted\n *11│//! endpoint the restart orphaned, resuming from its last ledger session.\n  12│//!\n  13│//! ## What this rig proves\n  14│//!\n *15│//! A fresh broker (an empty session table — exactly the state after a `daemon stop`)\n  16│//! plus a seeded previously-ONLINE, controllable (spt-hosted), relay-less, dead-custody\n  17│//! endpoint with a ledger row → the pass RE-SPAWNS its harness into a live broker PTY\n *18│//! session. RED-first: WITHOUT the pass the broker has no session for it (it stays\n  19│//! offline). The two belts are unit-proven in `livehost::restart_resume_gate_*`; this\n  20│//! rig proves the end-to-end re-run against the real broker + a real spawned harness.\n  21│//!\n    │...\n  33│\n *34│/// A long-lived, quiet harness the `[session.self]` resume spawns — it stays running\n  35│/// (so the broker session persists for the assertion) and produces no output.\n  36│#[cfg(windows)]\n  37│const HARNESS_CMD: &str = \"cmd /c ping -n 600 127.0.0.1 >nul\";\n    │...\n  41│/// Register a live-capable adapter whose `[session.self]` (the is_resume fallback when\n *42│/// no `[session.resume]`) spawns the long-lived quiet harness, so\n  43│/// `launch_harness_brokered_in` has a real program to run.\n  44│fn register_resume_adapter(name: &str) {\n  45│    let toml = format!(\n    │...\n  72│    // The ledger row the resume restores (session + adapter + cwd).\n *73│    let entry = spt_store::sessions::SessionEntry {\n  74│        ts: \"0\".to_string(),\n *75│        session_id: \"prev-session\".to_string(),\n *76│        trigger: spt_store::sessions::SessionTrigger::Boot,\n  77│        cwd: None,\n  78│        ordinal: None,\n  79│        adapter: Some(adapter.to_string()),\n    │...\n  90│\n *91│    // A FRESH broker at the well-known name — the empty-session state right after a\n *92│    // `daemon stop`+`start`. The pass's session-query + harness-launch both target it.\n  93│    let name = spt_daemon::broker_socket_name();\n  94│    let broker = Broker::bind(&name).expect(\"bind broker at the well-known name\");\n  95│    let serve = Arc::clone(&broker);\n    │...\n 110│\n*111│    // Precondition: the fresh broker hosts NO session for the endpoint (RED state).\n*112│    let sessions_before = live_session_ids(&name);\n 113│    assert!(\n 114│        !sessions_before.contains(&\"survivor\".to_string()),\n*115│        \"precondition: the fresh broker has no session for the orphaned endpoint\"\n 116│    );\n 117│\n 118│    // The pass under test — the one-shot daemon-restart survival re-run.\n    │...\n 123│\n*124│    // The endpoint's harness was re-run → a live broker session now exists for it.\n 125│    let mut appeared = false;\n 126│    let deadline = Instant::now() + Duration::from_secs(10);\n 127│    while Instant::now() < deadline {\n    │...\n 144│        \"the daemon-restart survival pass must re-run the orphaned endpoint's harness \\\n*145│         (a live broker session appears for it). RED without the pass: no session.\"\n 146│    );\n 147│}\n 148│\n*149│/// The endpoint ids the broker currently hosts a PTY session for (via a probe brain).\n*150│fn live_session_ids(name: &str) -> Vec<String> {\n 151│    let mut brain = match Brain::cold_start(name, now_ms()) {\n 152│        Ok(b) => b,\n 153│        Err(_) => return Vec::new(),\n    │...\n 161│/// The broker session id hosting the \"survivor\" endpoint, if any (for teardown).\n*162│fn survivor_session_id(name: &str) -> Option<u64> {\n 163│    let mut brain = Brain::cold_start(name, now_ms()).ok()?;\n 164│    brain\n 165│        .sessions()\n## false_promote.rs\n 260│        if f.kind == KIND_SPAWNED {\n*261│            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 262│        }\n 263│    };\n 264│    // Inject the statically-held wedge (doyle Option C): a LOCAL controller whose\n    │...\n 271│        broker.test_seed_wedged_local_controller(sid, 1_000),\n*272│        \"precondition: the wedge seed must install on the hosted session\"\n 273│    );\n 274│    drop(a);\n 275│\n## handoff.rs\n  11│//! then (c) \"crashes\" by dropping its connection. Throughout, the child's pid is\n *12│//! unchanged and exactly one session is hosted; `gen_start` advances every\n  13│//! handoff and the generation counter increments.\n  14│//!\n  15│//! A real-process SIGKILL of the brain was proven in Spike #1; here the brain is\n    │...\n  95│    let sid = brain\n *96│        .spawn_session(echo_spawn_req())\n *97│        .expect(\"spawn session\");\n  98│    let child_pid = broker.session_pid(sid);\n  99│    assert_eq!(brain.generation(), 0);\n 100│\n    │...\n 137│            1,\n*138│            \"exactly one session across restarts\"\n 139│        );\n 140│        assert_eq!(\n 141│            broker.session_pid(sid),\n    │...\n 179│    let mut brain = handoff_retry(&name, state, now);\n*180│    brain.kill_session().expect(\"kill session\");\n 181│    loop {\n*182│        if let BrokerEvent::Exit { session_id, .. } = brain.read_event().expect(\"read until exit\") {\n 183│            assert_eq!(session_id, sid);\n 184│            break;\n 185│        }\n## idempotent.rs\n 167│    let sid = brain\n*168│        .spawn_session(echo_spawn_req())\n*169│        .expect(\"spawn session\");\n 170│    let child_pid = broker.session_pid(sid);\n 171│\n 172│    let mut buf: Vec<u8> = Vec::new();\n    │...\n 194│            1,\n*195│            \"exactly one session across restarts\"\n 196│        );\n 197│        assert_eq!(\n 198│            broker.session_pid(sid),\n## inject_control_wedge.rs\n  14│//! stalled and any path needing that log lock (a fresh attach, or the global\n *15│//! `KIND_SESSIONS` enumeration, which reads each log's `delivered_through`)\n  16│//! blocked FOREVER, while the broker process stayed alive. The repro that proved\n  17│//! this (captured 2026-06-19): `APPEND_CTRL_ENTER sid=1 seq=2` with no matching\n *18│//! `APPEND_CTRL_EXIT`, `broker_alive=true` but `sessions_answered=false`.\n  19│//!\n  20│//! POST-FIX (W1): controller delivery moved off the drain thread onto a dedicated\n  21│//! writer thread + bounded channel (the viewer_writer pattern, but authoritative:\n    │...\n  24│//! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed\n *25│//! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test\n  26│//! asserts that fixed behavior.\n  27│//!\n  28│//! ## RUNNER CONTRACT — run process-per-test (nextest) or single-threaded (W1b F1)\n    │...\n  50│    encode_bytes, EndpointInputReq, SpawnReq, Spawned, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT,\n *51│    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n  52│    KIND_SPAWNED,\n  53│};\n  54│use spt_daemon::transport::{send_hello, LocalSocketTransport};\n    │...\n 108│/// controller_by viewer, the keystroke op, AND the W2 result-gating helpers\n*109│/// (`output_contains_within` / `observe_with_xon` / `spawn_xlate_session`). NOT a\n 110│/// connect deadline: `cold_start_pump`'s socket connect is UNBOUNDED — the `Duration`\n 111│/// arg is stored as `io_timeout` and applied as `call_deadline = now + io_timeout` to\n 112│/// every bounded read (`read_event_until`), never to `LocalSocketTransport::connect`\n    │...\n 167│/// Read `Broker::session_pid` under a bound. The pid is only used to best-effort\n*168│/// kill the flood child during teardown, but `session_pid` locks the broker's\n 169│/// `sessions` Mutex — if a wedged per-conn handler is holding that lock the read\n 170│/// blocks and rides teardown to the 240s backstop. Probe it on a detached thread\n 171│/// (the broker is an `Arc`, so the clone keeps it alive) and give up on timeout;\n 172│/// a missed child-kill is harmless, the process-per-test exit reaps it.\n*173│fn session_pid_bounded(broker: &Arc<Broker>, sid: u64, dur: Duration) -> Option<u32> {\n 174│    let broker = Arc::clone(broker);\n 175│    let (tx, rx) = std::sync::mpsc::channel();\n 176│    thread::spawn(move || {\n*177│        let _ = tx.send(broker.session_pid(sid));\n 178│    });\n 179│    rx.recv_timeout(dur).ok().flatten()\n 180│}\n    │...\n 210│/// A QUIET, long-lived child for the w5_a2 IDLE characterization (doyle gate\n*211│/// ruling, Option B): it produces NO output and does NOT exit, so the session is\n 212│/// GENUINELY idle for the read window. (`flood_spawn_req` floods through the read,\n 213│/// which makes b4's full-speed drain hit the dead-operator write-fail →\n 214│/// Disconnected-evict → controller_by clears — the rig bug that made w5_a2 a false\n    │...\n 285│#[test]\n*286│fn a_backed_up_controller_does_not_wedge_the_session() {\n 287│    let name = unique_name();\n 288│    let broker = Broker::bind(&name).expect(\"bind broker\");\n 289│    let serve_broker = Arc::clone(&broker);\n    │...\n 315│        if f.kind == KIND_SPAWNED {\n*316│            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 317│        }\n 318│    };\n 319│\n    │...\n 323│    //    the drain inside append HOLDING the log lock (permanent wedge). POST-FIX\n*324│    //    the send is OFF the log lock, so the session must stay responsive even\n 325│    //    while the controller is fully backed up. Probe DURING the stall (before\n 326│    //    CONTROLLER_WRITE_DEADLINE = 5s elapses). ──\n 327│    thread::sleep(Duration::from_secs(3));\n 328│\n*329│    // A fresh connection's KIND_NET_STATUS touches neither the sessions nor the\n*330│    // log lock (proves the process is alive). KIND_SESSIONS enumerates every\n 331│    // session's log (`delivered_through`) under each log lock — PRE-FIX it hung on\n 332│    // the parked session's held lock; POST-FIX it answers because the drain no\n 333│    // longer holds the lock during the controller send.\n    │...\n 335│    let sessions_answered =\n*336│        probe(&name, KIND_SESSIONS, KIND_SESSIONS_REPLY, Duration::from_secs(5));\n 337│\n 338│    eprintln!(\n 339│        \"=== W1 GATE: broker_alive(net_status)={broker_alive} \\\n*340│         sessions_answered={sessions_answered} (fixed = both true; pre-fix sessions=false) ===\"\n 341│    );\n 342│\n 343│    // Reap the flood child (scoped) + tear the broker down.\n    │...\n 355│        sessions_answered,\n*356│        \"a fully-backed-up controller must NOT wedge the session: KIND_SESSIONS \\\n 357│         (which reads each log's delivered_through under the log lock) must still \\\n 358│         answer because the controller write is delivered OFF the log lock on a \\\n 359│         dedicated writer thread (W1 fix). Pre-fix this hung — the inline \\\n    │...\n 364│#[test]\n*365│fn large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging() {\n*366│    // ADR-0022 amendment: idle delivery is translation-binary-ONLY. This session\n 367│    // declares NO translation binary, so a `KIND_ENDPOINT_INPUT` — even a 4 MiB one\n 368│    // — SPOOLS: the broker acks delivered=FALSE PROMPTLY and never touches\n 369│    // `write_input`, so the dispatch thread cannot park on a full PTY input buffer.\n    │...\n 403│        if f.kind == KIND_SPAWNED {\n*404│            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 405│        }\n 406│    };\n 407│    // Keep the controller socket drained so append never parks.\n## input_ack_deadlock.rs\n  50│//! Note on the per-conn liveness oracle: the broker serves each connection on its\n *51│//! OWN handler thread, so a fresh conn's `KIND_SESSIONS` can still answer even while\n  52│//! a DIFFERENT conn's handler is deadlocked (it only blocks if it needs that conn's\n  53│//! held lock). The load-bearing RED signal is therefore `flood_sent` (the deadlocked\n *54│//! conn itself), with `sessions_answered`/`broker_alive` as corroborating \"the rest\n  55│//! of the broker is alive\" oracles.\n  56│//!\n  57│//! ## RUNNER CONTRACT — single-threaded (mirrors inject_control_wedge.rs)\n    │...\n  75│use spt_daemon::msg::{\n *76│    decode_bytes, SpawnReq, KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_SESSIONS,\n *77│    KIND_SESSIONS_REPLY,\n  78│};\n  79│use spt_daemon::nethost::{NetConfig, NetHost};\n  80│use spt_daemon::transport::{send_hello, LocalSocketTransport};\n    │...\n 207│fn wait_for_stream(brain: &mut Brain) -> Option<(u64, String)> {\n*208│    for _ in 0..400 {\n 209│        let reply = brain.net_streams().expect(\"net-streams\");\n 210│        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 211│            return Some((s.stream_id, s.remote_id_hex.clone()));\n    │...\n 221│/// `false` — instead of hanging. Mirrors inject_control_wedge.rs's `probe`. This is\n*222│/// the broker-liveness oracle: `KIND_SESSIONS` enumerates every session (touches\n 223│/// the dispatch + session table), so a wedged handler that holds those makes it\n 224│/// time out; a serviceable broker answers.\n 225│fn probe(name: &str, cmd_kind: &'static str, want_kind: &'static str, deadline: Duration) -> bool {\n    │...\n 284│    let sid = spawner\n*285│        .spawn_session(quiet_spawn_req(endpoint))\n 286│        .expect(\"spawn quiet child\");\n 287│\n 288│    // ── (A) The FLOOD controller: dial loopback, request a CONTROLLER attach, then\n    │...\n 398│    //    oracle, on a FRESH direct conn (no second loopback dial to confound it).\n*399│    //    `KIND_SESSIONS` enumerates every session (touches the dispatch + session\n 400│    //    table the wedged flood handler would otherwise hold); `KIND_NET_STATUS`\n 401│    //    proves the process answers at all. Pre-fix the flood's per-conn handler is\n*402│    //    deadlocked and a fresh conn's KIND_SESSIONS never answers. ──\n 403│    let broker_alive =\n 404│        probe(&name, KIND_NET_STATUS, KIND_NET_STATUS_REPLY, Duration::from_secs(8));\n 405│    let sessions_answered =\n*406│        probe(&name, KIND_SESSIONS, KIND_SESSIONS_REPLY, Duration::from_secs(8));\n 407│\n 408│    // ── (B) The CONCURRENT viewer attach — the non-vacuous proof the broker is NOT\n 409│    //    wedged. A SEPARATE operator dials loopback, requests a VIEWER attach,\n    │...\n 532│        \"=== P1b ACK-DEADLOCK GATE: flood_n={FLOOD_N} flood_sent={flood_sent} \\\n*533│         broker_alive(net_status)={broker_alive} sessions_answered={sessions_answered} \\\n 534│         concurrent_attach_subscribed={subscribed} attach_received_output={got_output} \\\n*535│         (fixed = flood_sent + broker_alive + sessions_answered true; pre-fix on a \\\n 536│         small-pipe/forkpty platform: the applied-ack stream fills the return \\\n 537│         direction → the per-conn handler deadlocks → flood_sent=false. The loopback \\\n 538│         leg [subscribed/got_output] is a captured diagnostic, not asserted.) ===\"\n    │...\n 565│    );\n*566│    // (3) The rest of the broker stays serviceable: a fresh conn's KIND_SESSIONS —\n*567│    //     which enumerates every session through the dispatch + session table — is\n 568│    //     answered (the broker is not globally hung). Corroborating oracle: because\n 569│    //     each conn is served on its own handler thread, this can stay GREEN even when\n 570│    //     (1) is RED (only the flood's own conn deadlocks), so the load-bearing RED\n    │...\n 574│        sessions_answered,\n*575│        \"a fresh KIND_SESSIONS request must be SERVICED during/after the input flood — \\\n 576│         the broker's session table / dispatch must not be globally frozen \\\n 577│         (REQ-HAZARD-INPUT-ACK-BACKPRESSURE).\"\n 578│    );\n## netbroker.rs\n  25│use spt_daemon::effect::{EffectKey, Minter, MintedOp};\n *26│use spt_daemon::nethost::{NetConfig, NetHost, NET_EFFECT_SESSION};\n  27│use spt_daemon::Broker;\n  28│use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};\n  29│use spt_proto::identity::Identity;\n    │...\n 138│    let mut b_count = 0;\n*139│    for _ in 0..400 {\n 140│        b_count = brain_b.net_status().expect(\"b status\").conn_count;\n 141│        if b_count == 1 {\n 142│            break;\n    │...\n 183│    assert!(broker_a.journal().is_applied(EffectKey {\n*184│        class: NET_EFFECT_SESSION,\n 185│        minter: Minter::Cli,\n 186│        op: OP,\n 187│    }));\n    │...\n 291│        !broker_a.journal().is_applied(EffectKey {\n*292│            class: NET_EFFECT_SESSION,\n 293│            minter: Minter::Cli,\n 294│            op: OP,\n 295│        }),","truncation":{"content":"# crates/spt-daemon/tests/\n## access.rs\n 84|fn wait_for_stream(brain: &mut Brain) -> (u64, String) {\n*85|    for _ in 0..400 {\n 86|        let reply = brain.net_streams().expect(\"net-streams\");\n 87|        if let Some(s) = reply.streams.first() {\n 88|            return (s.stream_id, s.remote_id_hex.clone());\n## attach.rs\n 9|//! - **remote drive**: keystrokes sent from B arrive at A's PTY through the\n*10|//!   journaled input path, and the session's output streams back to B as\n 11|//!   sequenced records — B sees its own marker echoed by A's child;\n 12|//! - **origin is transport truth** (KNOWN-HAZARDS 7.5): the serve loop gates\n 13|//!   on the stream table's handshake-proven `remote_id_hex`;\n...\n 119|fn wait_for_stream(brain: &mut Brain) -> (u64, String) {\n*120|    for _ in 0..400 {\n 121|        let reply = brain.net_streams().expect(\"net-streams\");\n 122|        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {\n 123|            return (s.stream_id, s.remote_id_hex.clone());\n...\n 137|/// Given an acquired `conn_id` on `operator` and the `target` broker connection\n*138|/// holding `session_id` for `endpoint`: open the attach stream, serve it on\n 139|/// `target` under `expected_origin` (asserted == the stream table's proven\n 140|/// origin), drive `marker` through the PTY and render it back over the wire,\n*141|/// then detach and confirm the serve loop ended `Detached` (session outlives).\n 142|/// Returns the rendered viewport bytes + the (still-live) target brain so the\n*143|/// caller can assert session survival and clean up.\n 144|#[allow(clippy::too_many_arguments)]\n 145|fn attach_drive_detach(\n 146|    operator: &mut Brain,\n...\n 154|) -> (Vec<u8>, Brain) {\n*155|    let stream_b = request_attach(operator, conn_id, session_id, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n 156|    operator\n 157|        .net_stream_subscribe(stream_b, 0)\n 158|        .expect(\"operator subscribe\");\n...\n 191|\n*192|    // Detach: the viewport ends with the session still running.\n 193|    operator\n 194|        .net_stream_send(stream_b, &[], None, true)\n 195|        .expect(\"detach\");\n...\n 294|// streams back. This is the W1 gate proof — \"bring up + attach a real harness\n*295|// endpoint\" over the byte-identical cross-node attach pump; the session outlives\n 296|// the viewport (detach never kills it).\n 297|#[test]\n 298|fn spt_hosted_bringup_then_cross_node_attach_drives_the_pty() {\n...\n 304|\n*305|    // Bring up an spt-hosted harness on A. Its `[session.self]` is a real echo\n 306|    // binary standing in for a harness — the bringup spawns it into broker A's\n 307|    // PTY exactly as it would a `claude` command.\n 308|    #[cfg(unix)]\n...\n 313|        \"[adapter]\\nname = \\\"mock\\\"\\nkind = \\\"harness\\\"\\nversion = \\\"1\\\"\\n\\\n*314|         min_spt_core_version = \\\"0\\\"\\n\\n[session.self]\\ncommand = '{self_cmd}'\\n\"\n 315|    ))\n 316|    .expect(\"harness manifest\");\n 317|    let spawned = spt_daemon::harnesshost::launch_harness_brokered_in(\n...\n 321|\n*322|    // Operator on B attaches CROSS-NODE to the brought-up session.\n 323|    let mut target = connect_retry(&name_a);\n 324|    let a_addr = target.net_status().expect(\"a status\").addr;\n 325|    let mut operator = connect_retry(&name_b);\n...\n 332|    let stream_b =\n*333|        request_attach(&mut operator, conn.conn_id, spawned.session_id, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");\n 334|    operator\n 335|        .net_stream_subscribe(stream_b, 0)\n 336|        .expect(\"operator subscribe\");\n...\n 369|\n*370|// [unit->REQ-INST-8] drive an agent('s session) on machine A from machine B:\n 371|// B's keystrokes reach A's real PTY child over the wire (journaled input\n 372|// path), the child's output streams back as sequenced records, and B renders\n 373|// its own marker echoed remotely. The serve gate sees the handshake-proven\n*374|// origin; B detaching ends the viewport with the session still running on A.\n 375|#[test]\n 376|fn remote_attach_drives_a_real_pty_cross_daemon() {\n 377|    init_home();\n...\n 385|    let sid = target\n*386|        .spawn_session(echo_spawn_req())\n 387|        .expect(\"spawn echo child\");\n 388|    let a_addr = target.net_status().expect(\"a status\").addr;\n 389|\n...\n 417|// [int->REQ-RC-1] LOCAL attach rides the byte-identical pump (M12 W1.5, B1):\n*418|// a single broker spawns a real PTY session; the operator acquires a\n 419|// broker-minted in-process LOOPBACK conn (`net_dial_loopback` — NOT a QUIC\n 420|// self-dial, which iroh refuses) and drives the PTY through the SAME\n 421|// `serve_attach`+render pump a cross-node operator uses. The proof that \"both\n...\n 426|// origin is THIS node's own id, gating same-node (access short-circuit Allow in\n*427|// production; default-open in this hermetic home — both Allow). The session\n 428|// outlives the viewport.\n 429|#[test]\n 430|fn local_attach_via_loopback_conn_rides_the_same_pump() {\n...\n 440|    let sid = target\n*441|        .spawn_session(echo_spawn_req())\n 442|        .expect(\"spawn echo child\");\n 443|\n 444|    // The operator acquires the broker's loopback conn; its `remote_id_hex` is\n...\n 468|// [unit->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT] the replay-vs-forward deadlock pin:\n*469|// when the session's OutputLog ring is ALREADY NON-EMPTY at attach (an\n*470|// already-producing session — exactly the endpoint-run shape, whose transient\n 471|// cold-start spawn conn drops after `Spawned`), `become_controller` replays MANY\n 472|// frames into the SERVING brain while `serve_attach` must forward each back to the\n 473|// operator. Before the two-conn split both rode ONE broker IPC connection, so the\n## brain_decouple.rs\n 10|//! `CONTROLLER_WRITE_DEADLINE`) fires only on NEW output, and `reap_dead_controller`\n*11|//! only caught an EXITED writer — so a controller BLOCKED on an idle session was\n 12|//! evicted by neither: control latched, reattach was refused, `rc --take` hung,\n 13|//! `daemon status` still read healthy.\n 14|//!\n...\n 247|        if f.kind == KIND_SPAWNED {\n*248|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 249|        }\n 250|    };\n 251|\n 252|    // ── Remote controller R: a REMOTE operator (by=Some) takes control of the\n*253|    //    (locally-spawned, undriven) session, then STOPS reading — the suspended-\n 254|    //    brain analog. R MUST be a remote controller: a second remote's Control\n*255|    //    subscribe against a DIFFERENT-remote-driven session busy-refuses PRE-W2\n 256|    //    (the wedge), so only the stall-evict can free the slot — this is what\n 257|    //    isolates the fix from the pre-existing \"a remote takes an UNDRIVEN\n*258|    //    (local-only) session\" silent-control path. R's conn is held open (never\n 259|    //    read from) on its own thread so the broker's writer for R parks in\n 260|    //    write_frame exactly as against a suspended brain. ──\n 261|    let rname = name.clone();\n...\n 441|\n*442|/// One `KIND_SESSIONS` round-trip, returning the full reply. The dispatch runs\n 443|/// `reap_dead_controller` → the time-based `stall_evict_controller`, so polling\n 444|/// this both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 445|/// Bounded thread + channel (a wedge surfaces as a timeout, not a hang), like the\n...\n 448|#[cfg(windows)]\n*449|fn query_sessions(name: &str, deadline: Duration) -> Option<spt_daemon::msg::SessionsReply> {\n 450|    let name = name.to_string();\n 451|    let (tx, rx) = std::sync::mpsc::channel();\n 452|    thread::spawn(move || {\n 453|        let mut c = connect(&name);\n*454|        send(&mut c, spt_daemon::msg::KIND_SESSIONS, serde_json::Value::Null);\n 455|        loop {\n 456|            match read_frame(&mut c) {\n*457|                Ok(f) if f.kind == spt_daemon::msg::KIND_SESSIONS_REPLY => {\n*458|                    let r: spt_daemon::msg::SessionsReply =\n 459|                        serde_json::from_value(f.payload).unwrap();\n 460|                    let _ = tx.send(r);\n 461|                    return;\n...\n 496|        if f.kind == KIND_SPAWNED {\n*497|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 498|        }\n 499|    };\n 500|    drop(a);\n...\n 634|    // ── Step 6: exactly ONE logical stall eviction, observed via an INDEPENDENT\n*635|    //    DRAINING control-plane connection (the KIND_SESSIONS reap runs the 400 ms\n 636|    //    stall-evict). Record t_release at first observation and freeze the resume\n 637|    //    cursor for the sid row. ──\n 638|    // POISON-BEATS-EVICT (F-039 rider, doyle-ruled 2026-07-10, 3rd CI trip): the\n...\n 651|    while Instant::now() < step6_deadline {\n*652|        let sessions = query_sessions(&name, Duration::from_secs(3));\n 653|        let count = stall_evict_count(&name, Duration::from_secs(3));\n 654|        if count >= 1 {\n 655|            stall_evicts_seen = count;\n 656|            t_release = Some(Instant::now());\n*657|            frozen_cursor = sessions\n 658|                .as_ref()\n*659|                .and_then(|r| r.sessions.iter().find(|s| s.session_id == sid))\n 660|                .map(|s| s.resume_seq)\n 661|                .or_else(|| {\n 662|                    query_sessions(&name, Duration::from_secs(3)).and_then(|r| {\n...\n 664|                            .iter()\n*665|                            .find(|s| s.session_id == sid)\n 666|                            .map(|s| s.resume_seq)\n 667|                    })\n 668|                });\n...\n 693|            s,\n*694|            &Envelope::new(spt_daemon::msg::KIND_SESSIONS, serde_json::Value::Null),\n 695|        )\n 696|    });\n 697|    let old_client_write_is_err = old_client_write.as_ref().map(|r| r.is_err());\n## brain_resume_conn_deadlock.rs\n 4|//! Proves the code-read root (docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md), DEAD-PEER\n*5|//! INDEPENDENT: the daemon brain's `resume_sessions` subscribes broker PTY sessions\n 6|//! onto the brain's OWN request/reply conn. A conn's send half is one\n 7|//! `SharedSend = Arc<Mutex<SendHalf>>`; the subscriber writer threads\n 8|//! (`viewer_writer`/`controller_writer`) hold `send.lock()` ACROSS a BLOCKING\n 9|//! `write_frame`, and the dispatch reply path (`send_frame`) needs the SAME lock.\n*10|//! The daemon brain hosts no PTY sessions, so `run_brain` never drains that output —\n*11|//! it reads the conn only during its 500ms-heartbeat `net_status()`/`sessions()`\n*12|//! calls. When an actively-streaming session backs the conn up, the brain's own\n 13|//! subscriber writer blocks holding `send.lock()` → the heartbeat REPLY is starved →\n 14|//! the heartbeat stalls → the brain never drains → SELF-DEADLOCK. That is the field\n 15|//! wedge (both severities: respawn replay-burst + steady-state output backup), with\n...\n 112|\n*113|/// Spawn one flood PTY session; return (session_id, pid, spawner_conn). The conn is\n 114|/// kept alive (the spawner is the local by:None controller) but NOT drained — the\n 115|/// child floods the PTY regardless, so the broker's ring keeps advancing and the\n 116|/// brain's viewer subscription keeps receiving live output.\n...\n 127|            let s: Spawned = serde_json::from_value(f.payload).unwrap();\n*128|            return (s.session_id, s.pid.unwrap_or(0), c);\n 129|        }\n 130|    }\n 131|}\n...\n 133|/// How the probe brain resumes before running the heartbeat: the SHIPPING daemon\n*134|/// path — `CursorOnly` → `resume_session_cursors`, no subscribe. (The hazard path,\n 135|/// `resume_sessions`, is exercised directly by the deterministic zero-subscriber guard\n 136|/// `daemon_resume_leaves_zero_brain_subscribers` below, not through this probe.)\n 137|#[derive(Clone, Copy)]\n...\n 142|/// Drive `run_brain`'s heartbeat pattern on a fresh `Brain` conn: resume per `mode`,\n*143|/// then loop `net_status()` + `sessions()` — the exact per-tick IPC — incrementing a\n 144|/// shared counter per COMPLETED tick, so the caller can measure liveness in a window.\n 145|/// The probe NEVER drains subscribed output (it only issues request/reply, exactly like\n 146|/// the real non-draining daemon heartbeat) — so `CursorOnly` faithfully models the\n...\n 162|            ResumeMode::CursorOnly => {\n*163|                let _ = brain.resume_session_cursors();\n 164|            }\n 165|        }\n 166|        while !stop.load(Ordering::Relaxed) {\n...\n 170|            }\n*171|            if brain.sessions().is_err() {\n 172|                return;\n 173|            }\n 174|            ticks.fetch_add(1, Ordering::Relaxed);\n...\n 179|/// How many heartbeat ticks complete in `window` for a probe configured with `mode`.\n*180|/// N flood PTY sessions are already streaming before the probe starts.\n*181|fn heartbeat_ticks_under_flood(mode: ResumeMode, n_sessions: usize, window: Duration) -> u32 {\n 182|    init_home();\n 183|    let name = unique_name();\n 184|    let broker = Broker::bind(&name).expect(\"bind broker\");\n...\n 189|\n*190|    // Stand up N flooding PTY sessions the brain will resume.\n 191|    let mut conns = Vec::new();\n 192|    let mut pids = Vec::new();\n*193|    for i in 0..n_sessions {\n 194|        let (_sid, pid, conn) = spawn_flood(&name, &format!(\"flood-ep-{i}\"));\n 195|        pids.push(pid);\n 196|        conns.push(conn); // hold the spawner controller conns open (undrained)\n...\n 216|/// STEADY-STATE severity — the SHIPPING path: the daemon brain resumes CURSOR-ONLY\n*217|/// (`resume_session_cursors`, no subscribe) while N sessions actively stream, so its\n 218|/// heartbeat stays live. NO dead peer anywhere. RED-first: revert run_brain to the\n*219|/// subscribing `resume_sessions` (or see the regression-guard test below) and this\n 220|/// starves to a standstill.\n 221|// [int->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n 222|#[test]\n...\n 229|        ticks >= 100,\n*230|        \"the daemon's cursor-only resume must keep the heartbeat live under a {N}-session \\\n 231|         flood (got {ticks} ticks in {window:?}). A low count means the SharedSend \\\n 232|         self-deadlock re-appeared — the brain is subscribing a conn it does not drain.\"\n 233|    );\n...\n 236|/// RESPAWN-interleave severity — the SHIPPING path under the update-restart burst: a\n*237|/// fresh brain generation resumes CURSOR-ONLY while N sessions already flood at full\n 238|/// rate (the sessions predate this generation). The new generation's heartbeat — hence\n 239|/// its readiness/promotion — is never starved.\n 240|// [int->REQ-BRAIN-RESUME-NO-CONN-DEADLOCK]\n...\n 265|/// Two settled arms, no timing window:\n*266|///   POSITIVE — the shipping daemon path (`resume_session_cursors`) must leave EVERY\n 267|///   hosted session with ZERO viewers. A resume that instead subscribed the undrained\n 268|///   heartbeat conn re-opens the round-3 SharedSend self-deadlock class; post-r4 it\n 269|///   would instead get that heartbeat conn poisoned/retired by the write watchdog —\n...\n 272|///   takes the hazard path (`resume_sessions`, still present) and MUST raise every\n*273|///   session's viewer count to >= 1, proving `test_session_viewer_count` observes the\n 274|///   very subscription the positive arm forbids (a bare \"assert 0\" could pass vacuously\n 275|///   against an always-0 seam).\n 276|///\n## brain_swap.rs\n 15|//! produced while no brain was attached. Throughout, the child's pid is unchanged\n*16|//! and exactly one session is hosted: the endpoint never noticed the swap.\n 17|//!\n 18|//! **Filename note:** this file is `brain_swap.rs`, NOT `update.rs`, on purpose.\n 19|//! Windows' installer-detection heuristic (UAC) forces elevation for any\n...\n 109|    let sid = brain\n*110|        .spawn_session(echo_spawn_req())\n*111|        .expect(\"spawn session\");\n 112|    let child_pid = broker.session_pid(sid);\n 113|    assert_eq!(brain.generation(), 0);\n 114|\n...\n 147|        1,\n*148|        \"exactly one session across the update\"\n 149|    );\n 150|    assert_eq!(\n 151|        broker.session_pid(sid),\n...\n 173|    // The post-update brain still fully drives the same endpoint: kill it and see\n*174|    // the exit event + reaping — the session was live and owned, not a husk.\n*175|    brain.kill_session().expect(\"kill session\");\n 176|    loop {\n*177|        if let BrokerEvent::Exit { session_id, .. } = brain.read_event().expect(\"read until exit\") {\n 178|            assert_eq!(session_id, sid);\n 179|            break;\n 180|        }\n## broker.rs\n 144|        KIND_KILL,\n*145|        serde_json::to_value(KillReq { session_id: sid }).unwrap(),\n 146|    );\n 147|    let mut exit: Option<ExitEvent> = None;\n 148|    loop {\n...\n 160|    // Disconnect: dropping the single Stream closes the fd, so the broker's\n*161|    // command loop hits EOF and tears the session down. Joining the handler\n 162|    // (via the server thread) lets us assert the teardown happened.\n 163|    drop(conn);\n 164|    server.join().expect(\"server thread\");\n...\n 172|    assert_eq!(exit.session_id, sid);\n*173|    assert_eq!(broker.session_count(), 0, \"session torn down on disconnect\");\n 174|}\n 175|\n 176|fn accumulate_output(frame: &Envelope, out: &mut Vec<u8>) {\n...\n 187|/// inbound message addressed by ENDPOINT ID (`KIND_ENDPOINT_INPUT`) is delivered to\n*188|/// an spt-hosted session ONLY through its translation binary. With NO translation\n 189|/// binary declared (this echo child), idle delivery has no working binary → the\n 190|/// broker acks `delivered=false` and writes NOTHING to the PTY (the caller spools;\n 191|/// raw `payload+\\r` was removed as a delivery path — it did not submit on a modern\n...\n 235|\n*236|    // Endpoint-keyed inject (NO session_id — resolved by endpoint) into a hosted\n 237|    // session that declares NO translation binary: idle delivery is\n 238|    // translation-binary-ONLY, so there is no working binary → the broker acks\n 239|    // delivered=FALSE and writes NOTHING to the PTY (the caller spools).\n...\n 427|/// has EXITED surfaces a prompt Exit (dead-not-reaped → dispatch_subscribe's\n*428|/// try_wait → Exit frame) or a clean \"no such session\" error (already reaped) —\n 429|/// NEVER silence (the infinite-blank hang an attach would otherwise show). Spawn a\n 430|/// child that exits immediately, then poll-subscribe and assert a prompt\n 431|/// non-silent reply.\n 432|// [int->REQ-HAZARD-RC-ATTACH-FAILFAST]\n*433|fn dead_session_subscribe_test() {\n 434|    let name = unique_name();\n 435|    let broker = Broker::bind(&name).expect(\"bind broker\");\n 436|    let server_broker = Arc::clone(&broker);\n...\n 478|            Ok(f) if f.kind == KIND_SPAWNED => {\n*479|                sid = Some(serde_json::from_value::<Spawned>(f.payload).unwrap().session_id);\n 480|                break;\n 481|            }\n 482|            Ok(_) => {}\n...\n 487|\n*488|    // Poll-subscribe: every attempt must yield a prompt Exit or no-such-session\n 489|    // error — never an unanswered (silent) subscribe.\n 490|    let mut answered = false;\n 491|    for _ in 0..100 {\n...\n 525|#[test]\n*526|fn dead_session_subscribe_does_not_hang() {\n 527|    dead_session_subscribe_test();\n 528|}\n 529|\n...\n 578|        \"[adapter]\\nname = \\\"mock\\\"\\nkind = \\\"harness\\\"\\nversion = \\\"1\\\"\\n\\\n*579|         min_spt_core_version = \\\"0\\\"\\n\\n[session.self]\\ncommand = '{self_cmd}'\\n\\n\\\n 580|         [env.SPT_ENDPOINT_ID]\\ndirection = \\\"inject\\\"\\nvalue = \\\"{{id}}\\\"\\n\"\n 581|    );\n 582|    let manifest = spt_runtime::Manifest::from_toml_str(&manifest_toml).unwrap();\n...\n 617|    let mut saw_eid = false;\n*618|    for _ in 0..400 {\n 619|        if String::from_utf8_lossy(&out).contains(\"EID=wall-b\") {\n 620|            saw_eid = true;\n 621|            break;\n...\n 636|    // The send leg (ADR-0022 amendment / REQ-HAZARD-IDLE-SILENT-NONDELIVERY): an\n*637|    // endpoint-keyed inject to a session with NO translation binary SPOOLS — the\n 638|    // broker acks delivered=false and writes NOTHING to the PTY (raw inject removed;\n 639|    // a bare payload+\\r does not submit on a modern TUI). Read the\n 640|    // KIND_ENDPOINT_INJECTED ack on the SENDER conn — it DOES arrive — and assert\n...\n 707|\n*708|/// Spawn the long-lived echo child and return its session id (draining any\n 709|/// interleaved early output into `out`).\n 710|fn spawn_echo(conn: &mut Stream, out: &mut Vec<u8>) -> u64 {\n 711|    send(conn, KIND_SPAWN, serde_json::to_value(echo_spawn_req()).unwrap());\n...\n 759|/// REQ-HAZARD-INPUT-ACK-BACKPRESSURE — the ACK GATE, the crux unit. Against a\n*760|/// REAL broker hosting a REAL spawned PTY session, an `op_id`-carrying input with\n 761|/// `ack = false` produces NO `KIND_APPLIED` frame, while one with `ack = true`\n 762|/// does. This is the broker leg of the fix: `dispatch_input` writes the applied\n 763|/// envelope ONLY when `req.ack`, so the fire-and-forward operator/rc flood never\n...\n 810|        let mut probe_echoed = false;\n*811|        for _ in 0..4000 {\n 812|            if first_applied_op == Some(PROBE_OP) && noack_echoed && probe_echoed {\n 813|                break;\n 814|            }\n...\n 905|    // Drive the send + drain on a worker (the blocking `read_frame` can stall once\n*906|    // steady-state output stops) feeding back (session_id, echo_count). The main\n 907|    // thread bounds it with a watchdog, then reads the AUTHORITATIVE exactly-once\n 908|    // evidence straight off the broker-owned journal.\n 909|    let (tx, rx) = std::sync::mpsc::channel::<(u64, usize)>();\n...\n 1032|        if f.kind == KIND_SPAWNED {\n*1033|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 1034|        }\n 1035|    };\n 1036|\n...\n 1132|    // Send a UNIQUE post-subscribe marker; reading the consumer stream until it is\n*1133|    // ACCEPTED both bounds the read AND proves the session stayed LIVE through the\n 1134|    // double controller swap (input still reaches the PTY, output still flows).\n 1135|    send(\n 1136|        &mut conn,\n...\n 1193|        KIND_KILL,\n*1194|        serde_json::to_value(KillReq { session_id: sid }).unwrap(),\n 1195|    );\n 1196|    loop {\n 1197|        match read_frame(&mut conn) {\n## budget.rs\n 11|//!\n*12|//! A **dormant** seat is a live, state-preserved harness session held warm\n 13|//! by the broker (PTY master + child process + output ring). A **suspended**\n 14|//! seat is that session closed — only the on-disk perch record remains. So:\n 15|//!\n*16|//! 1. spawn N idle sessions through the real broker (the production PTY\n 17|//!    hosting path), labeled per seat;\n 18|//! 2. settle, then sample each child's RSS / handle (fd) count / CPU time,\n 19|//!    twice over an idle window (the delta = idle CPU burn);\n...\n 79|\n*80|    // One probe brain per seat (kill_session targets the brain's own session).\n*81|    let mut seats: Vec<(Brain, u64, u32)> = Vec::new(); // (brain, session, child pid)\n 82|    for i in 0..n {\n 83|        let mut brain = connect_retry(&name);\n 84|        let req = SpawnReq {\n...\n 95|        };\n*96|        let (session, pid) = brain.spawn_session_pid(req).expect(\"spawn seat\");\n 97|        let pid = pid.expect(\"seat child pid\");\n*98|        seats.push((brain, session, pid));\n 99|    }\n 100|\n 101|    thread::sleep(settle);\n...\n 153|\n*154|    // Suspend: close every seat (the session ends; only the perch record\n 155|    // would remain in production). The residual is the daemon baseline.\n 156|    for (brain, _, _) in seats.iter_mut() {\n*157|        let _ = brain.kill_session();\n 158|    }\n 159|    thread::sleep(Duration::from_millis(1_500));\n 160|    let self_after = sample_pid(std::process::id()).unwrap_or_default();\n## conn_blackhole_lifecycle.rs\n 14|//! src/conn.rs` bounded/cancelable/poison-on-failure writes, landed fc048bb),\n*15|//! asserts hertz's FIVE invariants for ONE incident:\n 16|//!\n*17|//!   1. UNRELATED SESSIONS CONTINUE — a second, independent session's viewer keeps\n 18|//!      receiving output throughout the black-hole incident (the fan-out never\n 19|//!      blocks the whole broker on the one wedged conn);\n 20|//!   2. THE BAD PHYSICAL CONNECTION IS CANCELED/CLOSED WITHIN THE BOUND — a write\n...\n 34|//!      PROXIES (this invariant has no direct seam): after retirement the broker\n*35|//!      still answers a control-plane `KIND_SESSIONS` request PROMPTLY (no lock\n 36|//!      is held by the retired writer), `conn_handler_count()` returns to the\n 37|//!      unrelated-viewer floor without the client dropping its end (no task\n 38|//!      lingers), AND both the\n*39|//!      unrelated session's viewer and the fresh viewer flow output (the shared\n 40|//!      fan-out lock is free). The conjunction of these three is the strongest\n 41|//!      deterministic proxy available for \"nothing is still owned.\"\n 42|//!\n...\n 53|//! not have, which are exactly hertz's invariants (1) and (4): a SECOND\n*54|//! UNRELATED session whose viewer keeps ticking across the incident, and a fresh\n 55|//! VIEWER attach (not just a controller take) proven to receive output.\n 56|//!\n 57|//! ## Why Windows-mandatory (`#![cfg(windows)]` — the whole file)\n...\n 89|use spt_daemon::msg::{\n*90|    decode_bytes, encode_bytes, InputReq, OutputEvent, SessionsReply, SpawnReq, Spawned,\n 91|    StallEvictsReply, SubscribeOutcome, SubscribeReq, SubscribedReply, KIND_INPUT, KIND_OUTPUT,\n*92|    KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN, KIND_SPAWNED, KIND_STALL_EVICTS,\n 93|    KIND_STALL_EVICTS_REPLY, KIND_SUBSCRIBE, KIND_SUBSCRIBED,\n 94|};\n 95|use spt_daemon::transport::{send_hello, LocalSocketTransport};\n...\n 182|\n*183|/// Spawn a PTY child from a fresh conn and return `(spawn_conn, session_id)`.\n*184|fn spawn_session(name: &str, req: SpawnReq) -> (Stream, u64) {\n 185|    let mut c = connect(name);\n 186|    send(&mut c, KIND_SPAWN, serde_json::to_value(req).unwrap());\n 187|    let sid = loop {\n...\n 189|        if f.kind == KIND_SPAWNED {\n*190|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 191|        }\n 192|    };\n 193|    (c, sid)\n...\n 221|\n*222|/// One `KIND_SESSIONS` round-trip, returning the reply. The dispatch runs the\n 223|/// `reap_dead_controller` → time-based `stall_evict_controller`, so polling this\n 224|/// both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 225|/// Bounded thread + channel. Verbatim sibling of the r4 gate helper.\n*226|fn query_sessions(name: &str, deadline: Duration) -> Option<SessionsReply> {\n 227|    let name = name.to_string();\n 228|    let (tx, rx) = std::sync::mpsc::channel();\n 229|    thread::spawn(move || {\n 230|        let mut c = connect(&name);\n*231|        send(&mut c, KIND_SESSIONS, serde_json::Value::Null);\n 232|        loop {\n 233|            match read_frame(&mut c) {\n*234|                Ok(f) if f.kind == KIND_SESSIONS_REPLY => {\n 235|                    let r: SessionsReply = serde_json::from_value(f.payload).unwrap();\n 236|                    let _ = tx.send(r);\n 237|                    return;\n...\n 248|#[test]\n*249|fn blackholed_controller_lifecycle_five_invariants() {\n 250|    init_home();\n 251|    // Shrink the brain-write deadline so the wedged writer is \"stalled\" in ~400 ms\n 252|    // instead of 15 s — the structural outcome, fast + deterministic (the knob\n...\n 262|\n*263|    // ── UNRELATED session U (invariant 1): a second, independent flood child on\n 264|    //    its OWN endpoint. Drop its spawner conn (so U has no controller to wedge\n 265|    //    — the PTY keeps producing headless), and attach a SEPARATE DRAINING\n 266|    //    viewer whose frame count must keep advancing ACROSS the whole incident. ──\n*267|    let (u_spawn, sid_u) = spawn_session(&name, flood_spawn_req(\"blackhole-unrelated-ep\"));\n 268|    drop(u_spawn);\n 269|    let u_name = name.clone();\n 270|    let u_frames = Arc::new(AtomicU64::new(0));\n...\n 293|    //    DROP A so the ONLY persistent brain conn is the black-holed controller R. ──\n*294|    let (a, sid_b) = spawn_session(&name, seed_burst_spawn_req(\"blackhole-victim-ep\"));\n 295|    drop(a);\n 296|\n 297|    // ── Remote controller R: subscribes Control from_seq 0, drains until it has\n...\n 435|    // ── Observe exactly ONE logical stall eviction via an INDEPENDENT DRAINING\n*436|    //    control-plane connection (the KIND_SESSIONS reap runs the 400 ms\n 437|    //    stall-evict). Record t_release + freeze the resume cursor for the sid_b\n 438|    //    row. Accept a tally of {1, 2}: POISON-BEATS-EVICT (F-039 rider,\n 439|    //    doyle-ruled) — the physical watchdog can poison first and record the same\n...\n 445|    while Instant::now() < step6_deadline {\n*446|        let sessions = query_sessions(&name, Duration::from_secs(3));\n 447|        let count = stall_evict_count(&name, Duration::from_secs(3));\n 448|        if count >= 1 {\n 449|            stall_evicts_seen = count;\n## control_stamp_lifetime.rs\n 9|//!\n*10|//! `/exit` ends the RC session INDIRECTLY: the endpoint suspends when the child\n 11|//! binary terminates, so the teardown runs the REAP path (exit-waiter →\n 12|//! `sessions.remove`), NOT the controller-detach path (`detach_if` →\n 13|//! `clear_controller` → `stamp_driven_by`, the ONLY pre-fix stamp-clear). The reap\n...\n 24|//! writer; the session is dead so no live controller re-stamps concurrently (the\n*25|//! same race-free argument as the W5 no-session self-heal).\n 26|//!\n 27|//! ## Why kill the CHILD (not drop the conn)\n 28|//!\n...\n 99|\n*100|/// A long-lived stdin-reading PTY child so the spawned session stays LIVE until we\n 101|/// kill it (cross-OS).\n 102|fn idle_spawn_req(endpoint: &str) -> SpawnReq {\n 103|    #[cfg(unix)]\n...\n 140|\n*141|    // ── A REAL broker hosting a LIVE session for THIS endpoint. dispatch_spawn\n 142|    //    pre-attaches the spawner as the LOCAL controller (by=None) and its\n 143|    //    become_controller → stamp_driven_by stamps `controlled=true` on the perch. ──\n 144|    let name = unique_name();\n...\n 147|    let sid = spawner\n*148|        .spawn_session(idle_spawn_req(&id))\n*149|        .expect(\"spawn live session for the endpoint\");\n 150|\n 151|    // Give the spawn's become_controller stamp a beat to land.\n 152|    thread::sleep(Duration::from_millis(200));\n 153|\n*154|    // Precondition (genuine): the live session stamped `controlled=true` (the local\n 155|    // controller). This is a REAL broker stamp, not a hand-set value.\n 156|    let before = spt_store::info::read_info(&self_perch).expect(\"info present\");\n 157|    assert!(\n 158|        before.controlled,\n*159|        \"precondition: the live hosted session must stamp controlled=true \\\n 160|         (become_controller → stamp_driven_by) — got {before:?}\"\n 161|    );\n 162|\n*163|    // Simulate the FULL latch a remote-driven, multi-viewer session leaves in\n 164|    // info.json: driven_by=Some(remote) + viewer_count=2 (broker is the single\n 165|    // writer of both; here we set the on-disk state the reap must unlatch). The\n 166|    // spawner stays attached, idle → nothing re-stamps these before the reap.\n...\n 217|        after.driven_by, None,\n*218|        \"reap must clear driven_by (a remote-controller latch must not outlive the session)\"\n 219|    );\n 220|    assert!(\n 221|        !after.controlled,\n...\n 234|/// uncontrolled forever while driven (hall-b / the original ball-b report). The\n*235|/// `KIND_SESSIONS` poll now CONVERGES the perch to the live session truth,\n 236|/// divergence-gated. RED-first: without the convergence hook, the perch stays\n 237|/// `controlled:false` forever.\n 238|#[test]\n*239|fn converge_stamps_on_sessions_poll_after_late_bind() {\n 240|    init_stamp_home();\n 241|    let id = format!(\"ctlconv-{}\", std::process::id());\n 242|\n*243|    // A REAL broker + a spawned session for the endpoint — but NO perch yet, so the\n 244|    // spawn's become_controller → set_controlled(true) is SWALLOWED (NotFound): the\n 245|    // picker-created stamp-before-bind loss.\n 246|    let name = unique_name();\n...\n 249|    let sid = spawner\n*250|        .spawn_session(idle_spawn_req(&id))\n*251|        .expect(\"spawn live session before its perch exists\");\n 252|    thread::sleep(Duration::from_millis(150));\n 253|\n 254|    // The adapter binds LATE, writing controlled:false (InfoJson::new default) — the\n...\n 262|\n*263|    // A KIND_SESSIONS poll (what the daemon reconcile does each cadence) converges the\n 264|    // perch to broker truth: the spawner is the (local) controller ⇒ controlled=true.\n 265|    let mut converged = false;\n*266|    for _ in 0..50 {\n 267|        let mut brain = connect_brain(&name);\n*268|        let _ = brain.sessions().expect(\"KIND_SESSIONS reply\");\n 269|        thread::sleep(Duration::from_millis(50));\n 270|        if spt_store::info::read_info(&self_perch)\n 271|            .map(|i| i.controlled)\n...\n 287|        converged,\n*288|        \"the KIND_SESSIONS poll must converge controlled=true within the window — got {after:?}\"\n 289|    );\n 290|    assert!(\n 291|        after.controlled,\n*292|        \"controlled converged to the live-session truth (a local controller drives it)\"\n 293|    );\n 294|    assert_eq!(\n 295|        after.driven_by, None,\n## daemon_e2e.rs\n 8|//! 1. **spawn** — a [`Broker`] hosts a real PTY child; a [`Brain`] cold-starts,\n*9|//!    spawns the session, and round-trips a marker through it (the session is live).\n 10|//! 2. **Psyche-loop** — a [`BrainLifecycle`] (the consolidated re-host of the\n 11|//!    interim `api listen` live half, [`REQ-DAEMON-1`]) spawns the Psyche companion;\n 12|//!    the daemon marks the hosted perch `online`, so its liveness is the daemon\n...\n 157|    let mut brain = cold_retry(&broker_name, now);\n*158|    let sid = brain.spawn_session(echo_req()).expect(\"spawn echo child\");\n 159|    let child_pid = broker.session_pid(sid);\n 160|    assert_eq!(brain.generation(), 0, \"cold start is generation 0\");\n 161|\n...\n 249|            1,\n*250|            \"exactly one session across restarts\"\n 251|        );\n 252|        assert_eq!(\n 253|            broker.session_pid(sid),\n...\n 310|\n*311|    // ── Reap: kill the child and confirm the broker drops the session ───────────\n 312|    now += 1;\n 313|    let mut brain = handoff_retry(&broker_name, state, now);\n*314|    brain.kill_session().expect(\"kill child\");\n 315|    drop(brain);\n 316|    let mut reaped = false;\n 317|    for _ in 0..200 {\n## digest.rs\n 66|    std::fs::create_dir_all(&perch_path).unwrap();\n*67|    let mut rec = InfoJson::new(id, \"0\", std::process::id(), \"cc-session\", \"live_agent\");\n 68|    rec.adapter = Some(adapter);\n 69|    write_info(&perch_path, &rec).unwrap();\n 70|}\n...\n 104|    // ── Snapshot pull: projected on demand from a harness log, no broker/PTY ──\n*105|    // A fixture JSONL of published digest-records — the harness's \"session log\".\n*106|    let fixture = home.path().join(\"cc-session.jsonl\");\n 107|    std::fs::write(\n 108|        &fixture,\n 109|        \"{\\\"role\\\":\\\"input\\\",\\\"text\\\":\\\"add a file\\\"}\\n\\\n...\n 131|    // ── Delta-stream: a reproject after the log grows delivers a delta ───────\n*132|    let fixture2 = home.path().join(\"cc2-session.jsonl\");\n 133|    std::fs::write(&fixture2, \"{\\\"role\\\":\\\"input\\\",\\\"text\\\":\\\"first\\\"}\\n\").unwrap();\n 134|    establish_cc_endpoint(\"cc2\", &fixture2);\n 135|\n## dispatch.rs\n 323|    let mut found = false;\n*324|    for _ in 0..400 {\n 325|        let rows = store_b.list(\"d91-notif\").expect(\"list\");\n 326|        if rows\n 327|            .iter()\n...\n 397|    let mut spooled = false;\n*398|    for _ in 0..400 {\n 399|        let pending = spool::peek_all_at(&perch_path).expect(\"peek\");\n 400|        if pending\n 401|            .iter()\n...\n 416|// [int->REQ-INST-8] [int->REQ-REACH-1] remote-drive is served end to end by\n*417|// the dispatcher: B hosts a LABELED session; A attaches by session id, types,\n 418|// and reads the echo — the gate subject came from the broker's table (the\n*419|// spawn label), not wire bytes. An UNLABELED session refuses, fail-closed.\n 420|#[test]\n 421|fn dispatcher_serves_a_remote_drive_attach_undriven() {\n 422|    init_home();\n...\n 429|\n*430|    // B hosts one labeled echo session and one unlabeled one.\n 431|    #[cfg(unix)]\n 432|    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n 433|    #[cfg(windows)]\n...\n 436|    let labeled = b_host\n*437|        .spawn_session(spt_daemon::msg::SpawnReq {\n 438|            program: program.clone(),\n 439|            args: args.clone(),\n 440|            rows: 24,\n...\n 450|    let unlabeled = b_host\n*451|        .spawn_session(spt_daemon::msg::SpawnReq {\n 452|            program,\n 453|            args,\n 454|            rows: 24,\n...\n 469|\n*470|    // Unlabeled session: the dispatcher refuses (our read side finishes\n 471|    // without a single Output record).\n 472|    let refused = spt_daemon::attach::request_attach(&mut a, conn.conn_id, unlabeled, 0, MintedOp::new(Minter::Rc, op()), spt_net::net::attach::AttachIntent::Control)\n 473|        .expect(\"open refused viewport\");\n...\n 484|            } if stream_id == refused && !bytes.is_empty() => {\n*485|                panic!(\"an unlabeled session must never serve output\");\n 486|            }\n 487|            _ => {}\n 488|        }\n...\n 525|// [int->REQ-RC-CROSS-NODE-ATTACH] the cross-node leg's SERVER half: an operator\n*526|// that cannot know B's local session id attaches BY ENDPOINT (session_id = 0\n*527|// placeholder + endpoint_id), and B resolves endpoint→its own session id\n 528|// server-side (it is authoritative for its table) — the echo returns, proving\n*529|// the attach landed on the right session without A ever naming a session id.\n*530|// A named endpoint with NO live session on B (the D6 stale-registry-row case)\n 531|// refuses cleanly: the stream finishes with no output, never a false attach.\n 532|#[test]\n 533|fn dispatcher_resolves_attach_by_endpoint_and_refuses_a_stale_endpoint() {\n...\n 541|\n*542|    // B hosts a live labeled echo session — but A is NOT told its session id.\n 543|    #[cfg(unix)]\n 544|    let (program, args) = (\"cat\".to_string(), Vec::<String>::new());\n 545|    #[cfg(windows)]\n...\n 548|    let _session = b_host\n*549|        .spawn_session(spt_daemon::msg::SpawnReq {\n 550|            program,\n 551|            args,\n 552|            rows: 24,\n...\n 567|\n*568|    // Attach BY ENDPOINT (session_id = 0 placeholder). B resolves it server-side.\n 569|    let viewport = spt_daemon::attach::request_attach_endpoint(\n 570|        &mut a,\n 571|        conn.conn_id,\n...\n 612|        String::from_utf8_lossy(&echoed).contains(\"by-endpoint\"),\n*613|        \"B resolved endpoint→session server-side and served the viewport; got {:?}\",\n 614|        String::from_utf8_lossy(&echoed)\n 615|    );\n 616|    // Detach the viewport cleanly before the refuse probe.\n...\n 618|\n*619|    // A STALE endpoint (no live session on B) refuses: clean finish, no output.\n 620|    let ghost = spt_daemon::attach::request_attach_endpoint(\n 621|        &mut a,\n 622|        conn.conn_id,\n...\n 646|                } if stream_id == ghost && !bytes.is_empty() => {\n*647|                    panic!(\"a stale endpoint with no live session must never serve output\");\n 648|                }\n 649|                _ => {}\n 650|            }\n...\n 654|        refused,\n*655|        \"a stale endpoint with no live session refuses with a clean stream finish\"\n 656|    );\n 657|    stop.store(true, Ordering::Relaxed);\n 658|}\n## driven_by_selfheal.rs\n 16|//!\n*17|//! - **Gap B (no session) — SHIPPED, GREEN.** The broker hosts NO session for the\n 18|//!   endpoint, but `info.json` is `status=online`, `controllable=Some(true)`,\n 19|//!   `driven_by=Some(node)`. `reconcile_hosted_liveness` marks the perch OFFLINE\n 20|//!   (`mark_offline` → `set_status(offline)`) AND clears `driven_by` to `None`\n 21|//!   (the `[impl->REQ-HAZARD-DRIVEN-BY-SELFHEAL]` block in `livehost.rs`, logging\n*22|//!   `DRIVEN_BY_SELFHEAL_OFFLINE`). RACE-FREE: with no live broker session there is\n 23|//!   no controller to re-stamp it concurrently. The Gap-B test below GREEN-locks\n 24|//!   this. (The original plan expected RED; the fix was already landed on this\n 25|//!   delivery branch — the test pins the GREEN behavior, not the buggy latch.)\n...\n 28|//!   The Gap-A test is NOT a pending RED bug-to-fix. It proves an INVARIANT: the\n*29|//!   reconcile MUST NOT clear `driven_by` for a perch that HAS a live session off a\n 30|//!   `controller_by==None` read, because that read is AMBIGUOUS — a live, LOCAL\n 31|//!   controller is pre-attached as `by=None` (`dispatch_spawn` →\n 32|//!   `become_controller(by=None)`), so `controller_by==None` cannot distinguish a\n 33|//!   lost controller from a live local one. A reconcile clear on `None` alone would\n*34|//!   FALSE-CLEAR a genuinely-driven session. The genuine production residual —\n*35|//!   an idle session whose ABANDONED REMOTE controller wedged without a clean\n 36|//!   detach — keeps `controller_by==Some(origin)`, which a reconcile read can't\n 37|//!   detect at all. That case is its own deferred requirement,\n 38|//!   `REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT` (broker-side controller eviction —\n...\n 43|//!\n*44|//! The broker's `KIND_SESSIONS` reply carries `SessionInfo.controller_by`\n 45|//! (additive, `#[serde(default)]`). The Gap-A test REPORTS, for the live session,\n 46|//! the exact `controller_by` value the real broker returns, and surfaces the\n*47|//! CRITICAL finding (see the banner + the assertions): a locally-spawned session is\n 48|//! pre-attached as the LOCAL controller with `by=None`, so a LIVE, locally-driven\n 49|//! session ALSO reports `controller_by==None`. `controller_by==None` therefore is\n 50|//! UNUSABLE as a Gap-A clear signal — proving why the reconcile must leave the\n...\n 121|/// Connect a Brain to the broker with bounded retry (cold_start — the request-reply\n*122|/// carrier `Brain::sessions()` rides; KIND_SESSIONS is a single reply, no streaming,\n 123|/// so a plain Whole conn is fine here).\n 124|fn connect_brain(name: &str) -> Brain {\n 125|    for _ in 0..300 {\n...\n 133|\n*134|/// A trivial long-lived PTY child so a spawned session stays LIVE (Gap A needs a\n 135|/// real live session in the broker table). Cross-OS: a stdin-reading idle process.\n 136|fn idle_spawn_req(endpoint: &str) -> SpawnReq {\n 137|    #[cfg(unix)]\n...\n 172|#[test]\n*173|fn gap_b_sessionless_perch_reconcile_offlines_and_clears_driven_by() {\n 174|    init_selfheal_home();\n 175|    let node = \"PEERNODEHEX_GAP_B\";\n 176|    // A unique endpoint id per test so the process-global owlery never collides.\n...\n 186|\n*187|    // ── A REAL broker that hosts NO session for this endpoint (Gap B = the\n*188|    //    harness is gone). Query its KIND_SESSIONS reply over the real IPC and\n 189|    //    confirm the endpoint is ABSENT (the broker really hosts nothing for it). ──\n 190|    let name = unique_name();\n 191|    let _broker = start_broker(&name);\n 192|    let mut brain = connect_brain(&name);\n*193|    let reply = brain.sessions().expect(\"KIND_SESSIONS reply\");\n 194|    let live_sessions: BTreeSet<String> =\n 195|        reply.sessions.iter().map(|s| s.endpoint.clone()).collect();\n 196|\n*197|    // OBSERVATION (item 2): the endpoint is absent from the broker's session set.\n*198|    let endpoint_absent = !live_sessions.contains(&id);\n 199|    eprintln!(\n*200|        \"=== W5 GAP-B OBSERVATION: endpoint_in_sessions={} (expect ABSENT=true) sessions={:?} ===\",\n 201|        live_sessions.contains(&id),\n 202|        live_sessions\n 203|    );\n...\n 205|        endpoint_absent,\n*206|        \"Gap-B precondition: the broker must host NO session for the endpoint \\\n*207|         (it is absent from KIND_SESSIONS) — sessions={live_sessions:?}\"\n 208|    );\n 209|\n 210|    // ── THE RECONCILE TICK: call the CURRENT reconcile_hosted_liveness directly\n*211|    //    with the broker's real (empty-for-this-endpoint) live-session set. ──\n*212|    let offlined = reconcile_hosted_liveness(&owlery_dir(), &live_sessions);\n 213|\n 214|    let info_after = spt_store::info::read_info(&self_perch).expect(\"info present after reconcile\");\n 215|    let status_after = info_after.status.clone();\n## endpoint_survival.rs\n 10|//! live-host start and RE-RUNS the harness of every previously-ONLINE spt-hosted\n*11|//! endpoint the restart orphaned, resuming from its last ledger session.\n 12|//!\n 13|//! ## What this rig proves\n 14|//!\n*15|//! A fresh broker (an empty session table — exactly the state after a `daemon stop`)\n 16|//! plus a seeded previously-ONLINE, controllable (spt-hosted), relay-less, dead-custody\n 17|//! endpoint with a ledger row → the pass RE-SPAWNS its harness into a live broker PTY\n*18|//! session. RED-first: WITHOUT the pass the broker has no session for it (it stays\n 19|//! offline). The two belts are unit-proven in `livehost::restart_resume_gate_*`; this\n 20|//! rig proves the end-to-end re-run against the real broker + a real spawned harness.\n 21|//!\n...\n 33|\n*34|/// A long-lived, quiet harness the `[session.self]` resume spawns — it stays running\n 35|/// (so the broker session persists for the assertion) and produces no output.\n 36|#[cfg(windows)]\n 37|const HARNESS_CMD: &str = \"cmd /c ping -n 600 127.0.0.1 >nul\";\n...\n 41|/// Register a live-capable adapter whose `[session.self]` (the is_resume fallback when\n*42|/// no `[session.resume]`) spawns the long-lived quiet harness, so\n 43|/// `launch_harness_brokered_in` has a real program to run.\n 44|fn register_resume_adapter(name: &str) {\n 45|    let toml = format!(\n...\n 72|    // The ledger row the resume restores (session + adapter + cwd).\n*73|    let entry = spt_store::sessions::SessionEntry {\n 74|        ts: \"0\".to_string(),\n*75|        session_id: \"prev-session\".to_string(),\n*76|        trigger: spt_store::sessions::SessionTrigger::Boot,\n 77|        cwd: None,\n 78|        ordinal: None,\n 79|        adapter: Some(adapter.to_string()),\n...\n 90|\n*91|    // A FRESH broker at the well-known name — the empty-session state right after a\n*92|    // `daemon stop`+`start`. The pass's session-query + harness-launch both target it.\n 93|    let name = spt_daemon::broker_socket_name();\n 94|    let broker = Broker::bind(&name).expect(\"bind broker at the well-known name\");\n 95|    let serve = Arc::clone(&broker);\n...\n 110|\n*111|    // Precondition: the fresh broker hosts NO session for the endpoint (RED state).\n*112|    let sessions_before = live_session_ids(&name);\n 113|    assert!(\n 114|        !sessions_before.contains(&\"survivor\".to_string()),\n*115|        \"precondition: the fresh broker has no session for the orphaned endpoint\"\n 116|    );\n 117|\n 118|    // The pass under test — the one-shot daemon-restart survival re-run.\n...\n 123|\n*124|    // The endpoint's harness was re-run → a live broker session now exists for it.\n 125|    let mut appeared = false;\n 126|    let deadline = Instant::now() + Duration::from_secs(10);\n 127|    while Instant::now() < deadline {\n...\n 144|        \"the daemon-restart survival pass must re-run the orphaned endpoint's harness \\\n*145|         (a live broker session appears for it). RED without the pass: no session.\"\n 146|    );\n 147|}\n 148|\n*149|/// The endpoint ids the broker currently hosts a PTY session for (via a probe brain).\n*150|fn live_session_ids(name: &str) -> Vec<String> {\n 151|    let mut brain = match Brain::cold_start(name, now_ms()) {\n 152|        Ok(b) => b,\n 153|        Err(_) => return Vec::new(),\n...\n 161|/// The broker session id hosting the \"survivor\" endpoint, if any (for teardown).\n*162|fn survivor_session_id(name: &str) -> Option<u64> {\n 163|    let mut brain = Brain::cold_start(name, now_ms()).ok()?;\n 164|    brain\n 165|        .sessions()\n## false_promote.rs\n 260|        if f.kind == KIND_SPAWNED {\n*261|            break serde_json::from_value::<Spawned>(f.payload).unwrap().session_id;\n 262|        }\n 263|    };\n 264|    // Inject the statically-held wedge (doyle Option C): a LOCAL controller whose\n...\n 271|        broker.test_seed_wedged_local_controller(sid, 1_000),\n*272|        \"precondition: the wedge seed must install on the hosted session\"\n 273|    );\n 274|    drop(a);\n 275|\n## handoff.rs\n 11|//! then (c) \"crashes\" by dropping its connection. Throughout, the child's pid is\n*12|//! unchanged and exactly one session is hosted; `gen_start` advances every\n 13|//! handoff and the generation counter increments.\n 14|//!\n 15|//! A real-process SIGKILL of the brain was proven in Spike #1; here the brain is\n...\n 95|    let sid = brain\n*96|        .spawn_session(echo_spawn_req())\n*97|        .expect(\"spawn session\");\n 98|    let child_pid = broker.session_pid(sid);\n 99|    assert_eq!(brain.generation(), 0);\n 100|\n...\n 137|            1,\n*138|            \"exactly one session across restarts\"\n 139|        );\n 140|        assert_eq!(\n 141|            broker.session_pid(sid),\n...\n 179|    let mut brain = handoff_retry(&name, state, now);\n*180|    brain.kill_session().expect(\"kill session\");\n 181|    loop {\n*182|        if let BrokerEvent::Exit { session_id, .. } = brain.read_event().expect(\"read until exit\") {\n 183|            assert_eq!(session_id, sid);\n 184|            break;\n 185|        }\n## idempotent.rs\n 167|    let sid = brain\n*168|        .spawn_session(echo_spawn_req())\n*169|        .expect(\"spawn session\");\n 170|    let child_pid = broker.session_pid(sid);\n 171|\n 172|    let mut buf: Vec<u8> = Vec::new();\n...\n 194|            1,\n*195|            \"exactly one session across restarts\"\n 196|        );\n 197|        assert_eq!(\n 198|            broker.session_pid(sid),\n## inject_control_wedge.rs\n 14|//! stalled and any path needing that log lock (a fresh attach, or the global\n*15|//! `KIND_SESSIONS` enumeration, which reads each log's `delivered_through`)\n 16|//! blocked FOREVER, while the broker process stayed alive. The repro that proved\n 17|//! this (captured 2026-06-19): `APPEND_CTRL_ENTER sid=1 seq=2` with no matching\n*18|//! `APPEND_CTRL_EXIT`, `broker_alive=true` but `sessions_answered=false`.\n 19|//!\n 20|//! POST-FIX (W1): controller delivery moved off the drain thread onto a dedicated\n 21|//! writer thread + bounded channel (the viewer_writer pattern, but authoritative:\n...\n 24|//! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed\n*25|//! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test\n 26|//! asserts that fixed behavior.\n 27|//!\n 28|//! ## RUNNER CONTRACT — run process-per-test (nextest) or single-threaded (W1b F1)\n...\n 50|    encode_bytes, EndpointInputReq, SpawnReq, Spawned, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT,\n*51|    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n 52|    KIND_SPAWNED,\n 53|};\n 54|use spt_daemon::transport::{send_hello, LocalSocketTransport};\n...\n 108|/// controller_by viewer, the keystroke op, AND the W2 result-gating helpers\n*109|/// (`output_contains_within` / `observe_with_xon` / `spawn_xlate_session`). NOT a\n 110|/// connect deadline: `cold_start_pump`'s socket connect is UNBOUNDED — the `Duration`\n 111|/// arg is stored as `io_timeout` and applied as `call_deadline = now + io_timeout` to\n 112|/// every bounded read (`read_event_until`), never to `LocalSocketTransport::connect`\n...","truncated":true,"truncatedBy":"bytes","totalLines":1211,"totalBytes":60155,"outputLines":1044,"outputBytes":51195,"lastLinePartial":false,"firstLineExceedsLimit":false},"meta":{"truncation":{"direction":"head","truncatedBy":"bytes","totalLines":1211,"totalBytes":60155,"outputLines":1044,"outputBytes":51195,"shownRange":{"start":1,"end":1044},"nextOffset":1045}}},"isError":false,"timestamp":1784247434376}}
{"type":"message","id":"50bdb7d5","parentId":"50b36534","timestamp":"2026-07-17T00:17:14.380Z","message":{"role":"toolResult","toolCallId":"call_YS3wCcW6q1DO0AxsUS8AalI4|fc_099a7567dadef391016a59748a22f0819bb9a2d0c9a42b250b","toolName":"read","content":[{"type":"text","text":"//! LIFECYCLE-TRUTH W3 (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL) — daemon-restart endpoint\n//! survival, against a REAL broker over the real local IPC.\n//!\n//! ## The bug this gate locks down (seed #6, the field-accept blocker)\n//!\n//! A `daemon stop` + `start` (the `update apply` notice's OWN instruction) kills every\n//! daemon-hosted harness; pre-W3 they stayed OFFLINE after start (no resurrection)\n//! though their records survived (info.json status + adapter + cwd + a ledger row). The\n//! fix: `livehost::resume_restart_orphaned_endpoints` runs ONCE at the fresh broker's\n//! live-host start and RE-RUNS the harness of every previously-ONLINE spt-hosted\n//! endpoint the restart orphaned, resuming from its last ledger session.\n//!\n//! ## What this rig proves\n//!\n//! A fresh broker (an empty session table — exactly the state after a `daemon stop`)\n//! plus a seeded previously-ONLINE, controllable (spt-hosted), relay-less, dead-custody\n//! endpoint with a ledger row → the pass RE-SPAWNS its harness into a live broker PTY\n//! session. RED-first: WITHOUT the pass the broker has no session for it (it stays\n//! offline). The two belts are unit-proven in `livehost::restart_resume_gate_*`; this\n//! rig proves the end-to-end re-run against the real broker + a real spawned harness.\n//!\n//! ## RUNNER CONTRACT — process-per-test (nextest) or single-threaded\n//!\n//! Owns the process-global `SPT_HOME` and binds the WELL-KNOWN `broker_socket_name()`\n//! (the pass's session-query + harness-launch both target it). Single `#[test]`.\n\nuse std::sync::Arc;\nuse std::thread;\nuse std::time::{Duration, Instant};\n\nuse spt_daemon::brain::now_ms;\nuse spt_daemon::{Brain, Broker};\n\n/// A long-lived, quiet harness the `[session.self]` resume spawns — it stays running\n/// (so the broker session persists for the assertion) and produces no output.\n#[cfg(windows)]\nconst HARNESS_CMD: &str = \"cmd /c ping -n 600 127.0.0.1 >nul\";\n#[cfg(unix)]\nconst HARNESS_CMD: &str = \"sleep 600\";\n\n/// Register a live-capable adapter whose `[session.self]` (the is_resume fallback when\n/// no `[session.resume]`) spawns the long-lived quiet harness, so\n/// `launch_harness_brokered_in` has a real program to run.\nfn register_resume_adapter(name: &str) {\n    let toml = format!(\n        \"[adapter]\\nname=\\\"{name}\\\"\\nkind=\\\"harness\\\"\\nversion=\\\"1\\\"\\n\\\n         min_spt_core_version=\\\"0\\\"\\n\\n\\\n         [session.self]\\ncommand='{HARNESS_CMD}'\\nkeys=[]\\n\"\n    );\n    let src = spt_store::perch::spt_home().join(\"srcs\").join(name);\n    std::fs::create_dir_all(&src).unwrap();\n    std::fs::write(src.join(\"manifest.toml\"), toml).unwrap();\n    spt_runtime::registry::register(&spt_store::perch::adapters_dir(), &src, 1000)\n        .expect(\"register the resume adapter\");\n}\n\n/// Seed a previously-ONLINE, spt-hosted (controllable), relay-less endpoint with a\n/// ledger row to resume from — the daemon-restart-orphaned shape.\nfn seed_orphaned_online_endpoint(id: &str, adapter: &str) {\n    use spt_store::perch::{resolve_perch_path, resolve_ready_file, ParentHint};\n    let perch = resolve_perch_path(id, ParentHint::Infer);\n    std::fs::create_dir_all(&perch).unwrap();\n    let mut rec = spt_store::info::InfoJson::new(id, \"0\", std::process::id(), \"sid-prev\", \"live_agent\");\n    rec.adapter = Some(adapter.to_string());\n    // controllable == Some(true): the doyle-Q1 spt-hosted discriminator (broker-PTY),\n    // NOT a user-terminal (Some(false)) or legacy (None) record.\n    rec.controllable = Some(true);\n    spt_store::info::write_info(&perch, &rec).unwrap();\n    // A real spt-hosted perch is online = daemon status + the ready marker.\n    spt_store::info::set_status(&perch, \"online\").unwrap();\n    std::fs::write(resolve_ready_file(id, ParentHint::Infer), \"\").unwrap();\n    // The ledger row the resume restores (session + adapter + cwd).\n    let entry = spt_store::sessions::SessionEntry {\n        ts: \"0\".to_string(),\n        session_id: \"prev-session\".to_string(),\n        trigger: spt_store::sessions::SessionTrigger::Boot,\n        cwd: None,\n        ordinal: None,\n        adapter: Some(adapter.to_string()),\n    };\n    spt_store::sessions::append(&perch, &entry).unwrap();\n    // NO resume.pid file → custody pid is not alive → the dup guard permits the re-run.\n}\n\n// [int->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\n#[test]\nfn daemon_restart_reruns_a_previously_online_orphaned_endpoint() {\n    let home = tempfile::TempDir::new().expect(\"temp home\");\n    std::env::set_var(\"SPT_HOME\", home.path());\n\n    // A FRESH broker at the well-known name — the empty-session state right after a\n    // `daemon stop`+`start`. The pass's session-query + harness-launch both target it.\n    let name = spt_daemon::broker_socket_name();\n    let broker = Broker::bind(&name).expect(\"bind broker at the well-known name\");\n    let serve = Arc::clone(&broker);\n    thread::spawn(move || {\n        let _ = serve.serve();\n    });\n    // Let the broker's accept loop come up.\n    for _ in 0..200 {\n        if Brain::cold_start(&name, now_ms()).is_ok() {\n            break;\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n\n    let adapter = \"mockresume\";\n    register_resume_adapter(adapter);\n    seed_orphaned_online_endpoint(\"survivor\", adapter);\n\n    // Precondition: the fresh broker hosts NO session for the endpoint (RED state).\n    let sessions_before = live_session_ids(&name);\n    assert!(\n        !sessions_before.contains(&\"survivor\".to_string()),\n        \"precondition: the fresh broker has no session for the orphaned endpoint\"\n    );\n\n    // The pass under test — the one-shot daemon-restart survival re-run.\n    let owlery = spt_store::perch::owlery_dir();\n    let adapters_dir = spt_store::perch::adapters_dir();\n    let registered = spt_runtime::registry::registered(&adapters_dir);\n    spt_daemon::livehost::resume_restart_orphaned_endpoints(&owlery, &registered, &adapters_dir);\n\n    // The endpoint's harness was re-run → a live broker session now exists for it.\n    let mut appeared = false;\n    let deadline = Instant::now() + Duration::from_secs(10);\n    while Instant::now() < deadline {\n        if live_session_ids(&name).contains(&\"survivor\".to_string()) {\n            appeared = true;\n            break;\n        }\n        thread::sleep(Duration::from_millis(50));\n    }\n\n    // Best-effort teardown: kill the long-lived harness child before asserting.\n    if let Some(sid) = survivor_session_id(&name) {\n        if let Some(pid) = broker.session_pid(sid) {\n            kill_pid(pid);\n        }\n    }\n\n    assert!(\n        appeared,\n        \"the daemon-restart survival pass must re-run the orphaned endpoint's harness \\\n         (a live broker session appears for it). RED without the pass: no session.\"\n    );\n}\n\n/// The endpoint ids the broker currently hosts a PTY session for (via a probe brain).\nfn live_session_ids(name: &str) -> Vec<String> {\n    let mut brain = match Brain::cold_start(name, now_ms()) {\n        Ok(b) => b,\n        Err(_) => return Vec::new(),\n    };\n    brain\n        .sessions()\n        .map(|r| r.sessions.into_iter().map(|s| s.endpoint).collect())\n        .unwrap_or_default()\n}\n\n/// The broker session id hosting the \"survivor\" endpoint, if any (for teardown).\nfn survivor_session_id(name: &str) -> Option<u64> {\n    let mut brain = Brain::cold_start(name, now_ms()).ok()?;\n    brain\n        .sessions()\n        .ok()?\n        .sessions\n        .into_iter()\n        .find(|s| s.endpoint == \"survivor\")\n        .map(|s| s.session_id)\n}\n\nfn kill_pid(pid: u32) {\n    #[cfg(windows)]\n    let _ = std::process::Command::new(\"taskkill\")\n        .args([\"/PID\", &pid.to_string(), \"/F\", \"/T\"])\n        .output();\n    #[cfg(unix)]\n    let _ = std::process::Command::new(\"kill\")\n        .args([\"-9\", &pid.to_string()])\n        .output();\n}\n"}],"details":{"displayContent":{"text":"//! LIFECYCLE-TRUTH W3 (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL) — daemon-restart endpoint\r\n//! survival, against a REAL broker over the real local IPC.\r\n//!\r\n//! ## The bug this gate locks down (seed #6, the field-accept blocker)\r\n//!\r\n//! A `daemon stop` + `start` (the `update apply` notice's OWN instruction) kills every\r\n//! daemon-hosted harness; pre-W3 they stayed OFFLINE after start (no resurrection)\r\n//! though their records survived (info.json status + adapter + cwd + a ledger row). The\r\n//! fix: `livehost::resume_restart_orphaned_endpoints` runs ONCE at the fresh broker's\r\n//! live-host start and RE-RUNS the harness of every previously-ONLINE spt-hosted\r\n//! endpoint the restart orphaned, resuming from its last ledger session.\r\n//!\r\n//! ## What this rig proves\r\n//!\r\n//! A fresh broker (an empty session table — exactly the state after a `daemon stop`)\r\n//! plus a seeded previously-ONLINE, controllable (spt-hosted), relay-less, dead-custody\r\n//! endpoint with a ledger row → the pass RE-SPAWNS its harness into a live broker PTY\r\n//! session. RED-first: WITHOUT the pass the broker has no session for it (it stays\r\n//! offline). The two belts are unit-proven in `livehost::restart_resume_gate_*`; this\r\n//! rig proves the end-to-end re-run against the real broker + a real spawned harness.\r\n//!\r\n//! ## RUNNER CONTRACT — process-per-test (nextest) or single-threaded\r\n//!\r\n//! Owns the process-global `SPT_HOME` and binds the WELL-KNOWN `broker_socket_name()`\r\n//! (the pass's session-query + harness-launch both target it). Single `#[test]`.\r\n\r\nuse std::sync::Arc;\r\nuse std::thread;\r\nuse std::time::{Duration, Instant};\r\n\r\nuse spt_daemon::brain::now_ms;\r\nuse spt_daemon::{Brain, Broker};\r\n\r\n/// A long-lived, quiet harness the `[session.self]` resume spawns — it stays running\r\n/// (so the broker session persists for the assertion) and produces no output.\r\n#[cfg(windows)]\r\nconst HARNESS_CMD: &str = \"cmd /c ping -n 600 127.0.0.1 >nul\";\r\n#[cfg(unix)]\r\nconst HARNESS_CMD: &str = \"sleep 600\";\r\n\r\n/// Register a live-capable adapter whose `[session.self]` (the is_resume fallback when\r\n/// no `[session.resume]`) spawns the long-lived quiet harness, so\r\n/// `launch_harness_brokered_in` has a real program to run.\r\nfn register_resume_adapter(name: &str) {\r\n    let toml = format!(\r\n        \"[adapter]\\nname=\\\"{name}\\\"\\nkind=\\\"harness\\\"\\nversion=\\\"1\\\"\\n\\\r\n         min_spt_core_version=\\\"0\\\"\\n\\n\\\r\n         [session.self]\\ncommand='{HARNESS_CMD}'\\nkeys=[]\\n\"\r\n    );\r\n    let src = spt_store::perch::spt_home().join(\"srcs\").join(name);\r\n    std::fs::create_dir_all(&src).unwrap();\r\n    std::fs::write(src.join(\"manifest.toml\"), toml).unwrap();\r\n    spt_runtime::registry::register(&spt_store::perch::adapters_dir(), &src, 1000)\r\n        .expect(\"register the resume adapter\");\r\n}\r\n\r\n/// Seed a previously-ONLINE, spt-hosted (controllable), relay-less endpoint with a\r\n/// ledger row to resume from — the daemon-restart-orphaned shape.\r\nfn seed_orphaned_online_endpoint(id: &str, adapter: &str) {\r\n    use spt_store::perch::{resolve_perch_path, resolve_ready_file, ParentHint};\r\n    let perch = resolve_perch_path(id, ParentHint::Infer);\r\n    std::fs::create_dir_all(&perch).unwrap();\r\n    let mut rec = spt_store::info::InfoJson::new(id, \"0\", std::process::id(), \"sid-prev\", \"live_agent\");\r\n    rec.adapter = Some(adapter.to_string());\r\n    // controllable == Some(true): the doyle-Q1 spt-hosted discriminator (broker-PTY),\r\n    // NOT a user-terminal (Some(false)) or legacy (None) record.\r\n    rec.controllable = Some(true);\r\n    spt_store::info::write_info(&perch, &rec).unwrap();\r\n    // A real spt-hosted perch is online = daemon status + the ready marker.\r\n    spt_store::info::set_status(&perch, \"online\").unwrap();\r\n    std::fs::write(resolve_ready_file(id, ParentHint::Infer), \"\").unwrap();\r\n    // The ledger row the resume restores (session + adapter + cwd).\r\n    let entry = spt_store::sessions::SessionEntry {\r\n        ts: \"0\".to_string(),\r\n        session_id: \"prev-session\".to_string(),\r\n        trigger: spt_store::sessions::SessionTrigger::Boot,\r\n        cwd: None,\r\n        ordinal: None,\r\n        adapter: Some(adapter.to_string()),\r\n    };\r\n    spt_store::sessions::append(&perch, &entry).unwrap();\r\n    // NO resume.pid file → custody pid is not alive → the dup guard permits the re-run.\r\n}\r\n\r\n// [int->REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL]\r\n#[test]\r\nfn daemon_restart_reruns_a_previously_online_orphaned_endpoint() {\r\n    let home = tempfile::TempDir::new().expect(\"temp home\");\r\n    std::env::set_var(\"SPT_HOME\", home.path());\r\n\r\n    // A FRESH broker at the well-known name — the empty-session state right after a\r\n    // `daemon stop`+`start`. The pass's session-query + harness-launch both target it.\r\n    let name = spt_daemon::broker_socket_name();\r\n    let broker = Broker::bind(&name).expect(\"bind broker at the well-known name\");\r\n    let serve = Arc::clone(&broker);\r\n    thread::spawn(move || {\r\n        let _ = serve.serve();\r\n    });\r\n    // Let the broker's accept loop come up.\r\n    for _ in 0..200 {\r\n        if Brain::cold_start(&name, now_ms()).is_ok() {\r\n            break;\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n\r\n    let adapter = \"mockresume\";\r\n    register_resume_adapter(adapter);\r\n    seed_orphaned_online_endpoint(\"survivor\", adapter);\r\n\r\n    // Precondition: the fresh broker hosts NO session for the endpoint (RED state).\r\n    let sessions_before = live_session_ids(&name);\r\n    assert!(\r\n        !sessions_before.contains(&\"survivor\".to_string()),\r\n        \"precondition: the fresh broker has no session for the orphaned endpoint\"\r\n    );\r\n\r\n    // The pass under test — the one-shot daemon-restart survival re-run.\r\n    let owlery = spt_store::perch::owlery_dir();\r\n    let adapters_dir = spt_store::perch::adapters_dir();\r\n    let registered = spt_runtime::registry::registered(&adapters_dir);\r\n    spt_daemon::livehost::resume_restart_orphaned_endpoints(&owlery, &registered, &adapters_dir);\r\n\r\n    // The endpoint's harness was re-run → a live broker session now exists for it.\r\n    let mut appeared = false;\r\n    let deadline = Instant::now() + Duration::from_secs(10);\r\n    while Instant::now() < deadline {\r\n        if live_session_ids(&name).contains(&\"survivor\".to_string()) {\r\n            appeared = true;\r\n            break;\r\n        }\r\n        thread::sleep(Duration::from_millis(50));\r\n    }\r\n\r\n    // Best-effort teardown: kill the long-lived harness child before asserting.\r\n    if let Some(sid) = survivor_session_id(&name) {\r\n        if let Some(pid) = broker.session_pid(sid) {\r\n            kill_pid(pid);\r\n        }\r\n    }\r\n\r\n    assert!(\r\n        appeared,\r\n        \"the daemon-restart survival pass must re-run the orphaned endpoint's harness \\\r\n         (a live broker session appears for it). RED without the pass: no session.\"\r\n    );\r\n}\r\n\r\n/// The endpoint ids the broker currently hosts a PTY session for (via a probe brain).\r\nfn live_session_ids(name: &str) -> Vec<String> {\r\n    let mut brain = match Brain::cold_start(name, now_ms()) {\r\n        Ok(b) => b,\r\n        Err(_) => return Vec::new(),\r\n    };\r\n    brain\r\n        .sessions()\r\n        .map(|r| r.sessions.into_iter().map(|s| s.endpoint).collect())\r\n        .unwrap_or_default()\r\n}\r\n\r\n/// The broker session id hosting the \"survivor\" endpoint, if any (for teardown).\r\nfn survivor_session_id(name: &str) -> Option<u64> {\r\n    let mut brain = Brain::cold_start(name, now_ms()).ok()?;\r\n    brain\r\n        .sessions()\r\n        .ok()?\r\n        .sessions\r\n        .into_iter()\r\n        .find(|s| s.endpoint == \"survivor\")\r\n        .map(|s| s.session_id)\r\n}\r\n\r\nfn kill_pid(pid: u32) {\r\n    #[cfg(windows)]\r\n    let _ = std::process::Command::new(\"taskkill\")\r\n        .args([\"/PID\", &pid.to_string(), \"/F\", \"/T\"])\r\n        .output();\r\n    #[cfg(unix)]\r\n    let _ = std::process::Command::new(\"kill\")\r\n        .args([\"-9\", &pid.to_string()])\r\n        .output();\r\n}\r\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests\\endpoint_survival.rs"}}},"isError":false,"timestamp":1784247434380}}
{"type":"message","id":"8952e2a1","parentId":"50bdb7d5","timestamp":"2026-07-17T00:17:14.384Z","message":{"role":"toolResult","toolCallId":"call_aQYXcmsbYCSwf0xbQH7i86jW|fc_099a7567dadef391016a59748a22f4819b9eed0a621073235a","toolName":"read","content":[{"type":"text","text":"fn file_hash(path: &std::path::Path) -> String {\n…\n    let mut h = Sha256::new();\n    h.update(&bytes);\n    let digest = h.finalize();\n    let mut s = String::with_capacity(digest.len() * 2);\n    for b in digest {\n        s.push_str(&format!(\"{b:02x}\"));\n    }\n    s\n}\n\n#[test]\nfn pty_and_quic_survive_brain_process_restart_onto_swapped_binary() {\n    // The in-process broker + brain children both resolve `broker_socket_name()`\n    // from SPT_HOME, so the supervised child connects to OUR broker. (The\n    // established daemon-test pattern — daemon_e2e.rs et al. set it the same way.)\n    let home = tempfile::tempdir().expect(\"tempdir\");\n    // Single-test binary by design — this is the SOLE `#[test]` here, so it owns\n    // the process-global SPT_HOME on its own thread (cross-binary tests are\n    // separate processes). A SECOND `#[test]` added to this file MUST bring the\n    // env-lock discipline (a shared mutex / serialized home), or it will race this\n    // one — the D6 env-race lesson (see the spt-daemon test_home pattern).\n    std::env::set_var(\"SPT_HOME\", home.path());\n\n    // ── Two fixtures: A, and B = A + appended padding (the on-disk \"swap\"). ──\n    // Both are the real `spt` binary, so `daemon brain` runs; B differs only by\n    // trailing bytes (behavior identical, exe_hash flips).\n    let spt_bin = PathBuf::from(env!(\"CARGO_BIN_EXE_spt\"));\n    let suffix = spt_bin.extension().map(|e| format!(\".{}\", e.to_string_lossy())).unwrap_or_default();\n    let fixture_a = home.path().join(format!(\"brain-A{suffix}\"));\n    let fixture_b = home.path().join(format!(\"brain-B{suffix}\"));\n    std::fs::copy(&spt_bin, &fixture_a).expect(\"stage fixture A\");\n    std::fs::copy(&spt_bin, &fixture_b).expect(\"stage fixture B\");\n    {\n        use std::io::Write;\n        let mut f = std::fs::OpenOptions::new()\n            .append(true)\n            .open(&fixture_b)\n            .expect(\"open fixture B to append padding\");\n        f.write_all(&[0u8; 4096]).expect(\"append padding to fixture B\");\n    }\n    let hash_a = file_hash(&fixture_a);\n    let hash_b = file_hash(&fixture_b);\n    assert_ne!(hash_a, hash_b, \"the swap must flip the binary hash\");\n\n    // ── The broker (leg-b stable kernel) + a loopback QUIC peer. ──\n    let broker = net_broker(&broker_socket_name(), Identity::generate(), &home.path().join(\"a\"));\n    let peer_name = format!(\"spt-d7-peer-{}.sock\", std::process::id());\n    let _peer = net_broker(&peer_name, Identity::generate(), &home.path().join(\"b\"));\n\n    // ── Establish the two endpoints ONCE via a test-driver brain, then drop it\n    //    HARD (the D4 hard-kill posture — the outgoing side is gone before the\n    //    supervised child resumes). ──\n    let (sid, child_pid) = {\n        let mut driver = connect_retry(&broker_socket_name());\n\n        // (1) A hosted PTY child the broker holds.\n        let sid = driver.spawn_session(echo_spawn_req()).expect(\"spawn session\");\n        let child_pid = broker.session_pid(sid).expect(\"hosted child has a pid\");\n\n        // (2) A live QUIC connection the broker holds: dial the loopback peer.\n        let peer_addr = {\n            let mut peer_brain = connect_retry(&peer_name);\n            peer_brain.net_status().expect(\"peer status\").addr\n        };\n        driver.net_dial(peer_addr, None).expect(\"dial the loopback peer\");\n\n        // Both endpoints are now broker-held.\n        assert_eq!(broker.session_count(), 1, \"one hosted session established\");\n        assert_eq!(broker.net_conn_count(), 1, \"one live QUIC conn established\");\n        (sid, child_pid)\n        // driver dropped here — HARD detach (no graceful handoff frame).\n    };\n\n    // ── Start the production supervisor, spawning the REAL `spt daemon brain`\n    //    child from the currently-selected fixture (A). NoTrialEnv = the\n    //    pre-D6 respawn loop (no readiness gate); the swap is driven by the\n    //    planned-restart signal, exactly as a real `apply` drives it (D3-3). ──\n    let ready_path = home.path().join(\"brain.ready\");\n    let stop = Arc::new(AtomicBool::new(false));\n    let restart = Arc::new(BrainRestart::new());\n    let selected = Arc::new(Mutex::new(fixture_a.clone()));\n    let home_path = home.path().to_path_buf();\n\n    let sup = {\n        let stop = Arc::clone(&stop);\n        let restart = Arc::clone(&restart);\n        let selected = Arc::clone(&selected);\n        thread::spawn(move || {\n            supervise_brain(\n                &stop,\n                &restart,\n                Duration::from_millis(50),\n                &NoTrialEnv,\n                Duration::from_secs(30),\n                move |gen, reason: StartReason, _binary| {\n                    let exe = selected.lock().unwrap().clone();\n                    Command::new(&exe)\n                        .no_window()\n                        .args([\n                            \"daemon\",\n                            \"brain\",\n                            \"--generation\",\n                            &gen.to_string(),\n                            \"--start-reason\",\n                            reason.as_arg(),\n                        ])\n                        .env(\"SPT_HOME\", &home_path)\n                        .spawn()\n                },\n            );\n        })\n    };\n\n    // ── Generation 0: the real child came up from fixture A, resumed the hosted\n    //    session (the first N≥1 cursor-of-record resume), and stamped exe_hash A. ──\n    let (pid0, _gen0, hash0) = wait_ready(&ready_path, None, Duration::from_secs(45))\n        .unwrap_or_else(|| teardown_panic(&stop, \"gen-0 brain never signalled ready\"));\n    assert_eq!(\n        hash0.as_deref(),\n        Some(hash_a.as_str()),\n        \"gen-0 brain.ready exe_hash must be fixture A\"\n    );\n    assert_eq!(broker.session_count(), 1, \"session survived the driver drop + resume\");\n    assert_eq!(\n        broker.session_pid(sid),\n        Some(child_pid),\n        \"the PTY child's pid is unchanged through the resume\"\n    );\n    assert_eq!(broker.net_conn_count(), 1, \"the QUIC conn survived the driver drop\");\n\n    // ── SWAP: flip the selected binary to B, then trigger a planned restart\n    //    (what `apply` does: swap on disk, signal the brain to cycle). ──\n    *selected.lock().unwrap() = fixture_b.clone();\n    restart.request();\n\n    // ── Generation 1: the supervisor respawned the brain PROCESS onto the\n    //    SWAPPED binary. New pid, exe_hash == B != A (NEW BYTES RAN). ──\n    let (pid1, _gen1, hash1) = wait_ready(&ready_path, Some(pid0), Duration::from_secs(45))\n        .unwrap_or_else(|| teardown_panic(&stop, \"gen-1 brain never respawned onto the swap\"));\n    assert_ne!(pid0, pid1, \"the respawned brain is a fresh process\");\n    assert_eq!(\n        hash1.as_deref(),\n        Some(hash_b.as_str()),\n        \"post-swap exe_hash must be fixture B — the supervisor ran the NEW bytes, \\\n         not a relaunch of the old (the property brain_swap.rs cannot prove)\"\n    );\n    assert_ne!(\n        hash1.as_deref(),\n        Some(hash_a.as_str()),\n        \"post-swap exe_hash must differ from the pre-swap binary\"\n    );\n\n    // ── THE SURVIVAL INVARIANT: both broker-held endpoints are untouched across\n    //    the brain-PROCESS swap. ──\n    assert_eq!(\n        broker.session_pid(sid),\n        Some(child_pid),\n        \"REQ-UPD-3: the hosted PTY child's pid is unchanged across the brain-process swap\"\n    );\n    assert_eq!(broker.session_count(), 1, \"exactly one hosted session throughout\");\n    assert_eq!(\n        broker.net_conn_count(),\n        1,\n        \"the live QUIC conn the broker holds survived the brain-process swap (SPIKE-03 in production topology)\"\n    );\n\n    // ── FUNCTIONAL survival (doyle F1, plan choreography 5(d)): \"held\" is not\n    //    \"alive\". Drive a marker through the held PTY child POST-swap and watch the\n    //    broker-side output seq advance — proving the full round-trip (input →\n    //    child → echo → broker ring) works through the swapped-in brain, not that\n    //    a zombie handle merely lingers. The input rides the KIND_INPUT effect verb\n    //    on a FRESH raw connection, which does NOT steal the supervised brain's\n    //    output subscription (it is not an attach). ──\n    let seq_before = broker\n        .session_output_seq(sid)\n        .unwrap_or_else(|| teardown_panic(&stop, \"hosted session vanished before the functional probe\"));\n    {\n        let mut raw = LocalSocketTransport::connect(&broker_socket_name())\n            .unwrap_or_else(|_| teardown_panic(&stop, \"could not open a raw input connection\"));\n        send_hello(&mut raw, Role::Brain)\n            .unwrap_or_else(|_| teardown_panic(&stop, \"raw hello failed\"));\n        let input = InputReq {\n            session_id: sid,\n            data_b64: encode_bytes(b\"D7-1 POST-SWAP FUNCTIONAL PROBE\\r\"),\n            op_id: None,\n            minter: None,\n            ack: true,\n        };\n        write_frame(\n            &mut raw,\n            &Envelope::new(KIND_INPUT, serde_json::to_value(input).expect(\"InputReq serializes\")),\n        )\n        .unwrap_or_else(|_| teardown_panic(&stop, \"could not write the marker input frame\"));\n    }\n    // The echo child (cat/findstr) echoes the marker → output appended → next_seq\n    // advances. Poll the broker's own log high-water.\n    let mut advanced = false;\n    let deadline = Instant::now() + Duration::from_secs(15);\n    while Instant::now() < deadline {\n        if broker.session_output_seq(sid).map(|s| s > seq_before).unwrap_or(false) {\n            advanced = true;\n            break;\n        }\n        thread::sleep(Duration::from_millis(50));\n    }\n…\n}\n\n[Showing lines 179-383 of 404. Use :384 to continue]"}],"details":{"truncation":{"content":"    let mut h = Sha256::new();\r\n    h.update(&bytes);\r\n    let digest = h.finalize();\r\n    let mut s = String::with_capacity(digest.len() * 2);\r\n    for b in digest {\r\n        s.push_str(&format!(\"{b:02x}\"));\r\n    }\r\n    s\r\n}\r\n\r\n#[test]\r\nfn pty_and_quic_survive_brain_process_restart_onto_swapped_binary() {\r\n    // The in-process broker + brain children both resolve `broker_socket_name()`\r\n    // from SPT_HOME, so the supervised child connects to OUR broker. (The\r\n    // established daemon-test pattern — daemon_e2e.rs et al. set it the same way.)\r\n    let home = tempfile::tempdir().expect(\"tempdir\");\r\n    // Single-test binary by design — this is the SOLE `#[test]` here, so it owns\r\n    // the process-global SPT_HOME on its own thread (cross-binary tests are\r\n    // separate processes). A SECOND `#[test]` added to this file MUST bring the\r\n    // env-lock discipline (a shared mutex / serialized home), or it will race this\r\n    // one — the D6 env-race lesson (see the spt-daemon test_home pattern).\r\n    std::env::set_var(\"SPT_HOME\", home.path());\r\n\r\n    // ── Two fixtures: A, and B = A + appended padding (the on-disk \"swap\"). ──\r\n    // Both are the real `spt` binary, so `daemon brain` runs; B differs only by\r\n    // trailing bytes (behavior identical, exe_hash flips).\r\n    let spt_bin = PathBuf::from(env!(\"CARGO_BIN_EXE_spt\"));\r\n    let suffix = spt_bin.extension().map(|e| format!(\".{}\", e.to_string_lossy())).unwrap_or_default();\r\n    let fixture_a = home.path().join(format!(\"brain-A{suffix}\"));\r\n    let fixture_b = home.path().join(format!(\"brain-B{suffix}\"));\r\n    std::fs::copy(&spt_bin, &fixture_a).expect(\"stage fixture A\");\r\n    std::fs::copy(&spt_bin, &fixture_b).expect(\"stage fixture B\");\r\n    {\r\n        use std::io::Write;\r\n        let mut f = std::fs::OpenOptions::new()\r\n            .append(true)\r\n            .open(&fixture_b)\r\n            .expect(\"open fixture B to append padding\");\r\n        f.write_all(&[0u8; 4096]).expect(\"append padding to fixture B\");\r\n    }\r\n    let hash_a = file_hash(&fixture_a);\r\n    let hash_b = file_hash(&fixture_b);\r\n    assert_ne!(hash_a, hash_b, \"the swap must flip the binary hash\");\r\n\r\n    // ── The broker (leg-b stable kernel) + a loopback QUIC peer. ──\r\n    let broker = net_broker(&broker_socket_name(), Identity::generate(), &home.path().join(\"a\"));\r\n    let peer_name = format!(\"spt-d7-peer-{}.sock\", std::process::id());\r\n    let _peer = net_broker(&peer_name, Identity::generate(), &home.path().join(\"b\"));\r\n\r\n    // ── Establish the two endpoints ONCE via a test-driver brain, then drop it\r\n    //    HARD (the D4 hard-kill posture — the outgoing side is gone before the\r\n    //    supervised child resumes). ──\r\n    let (sid, child_pid) = {\r\n        let mut driver = connect_retry(&broker_socket_name());\r\n\r\n        // (1) A hosted PTY child the broker holds.\r\n        let sid = driver.spawn_session(echo_spawn_req()).expect(\"spawn session\");\r\n        let child_pid = broker.session_pid(sid).expect(\"hosted child has a pid\");\r\n\r\n        // (2) A live QUIC connection the broker holds: dial the loopback peer.\r\n        let peer_addr = {\r\n            let mut peer_brain = connect_retry(&peer_name);\r\n            peer_brain.net_status().expect(\"peer status\").addr\r\n        };\r\n        driver.net_dial(peer_addr, None).expect(\"dial the loopback peer\");\r\n\r\n        // Both endpoints are now broker-held.\r\n        assert_eq!(broker.session_count(), 1, \"one hosted session established\");\r\n        assert_eq!(broker.net_conn_count(), 1, \"one live QUIC conn established\");\r\n        (sid, child_pid)\r\n        // driver dropped here — HARD detach (no graceful handoff frame).\r\n    };\r\n\r\n    // ── Start the production supervisor, spawning the REAL `spt daemon brain`\r\n    //    child from the currently-selected fixture (A). NoTrialEnv = the\r\n    //    pre-D6 respawn loop (no readiness gate); the swap is driven by the\r\n    //    planned-restart signal, exactly as a real `apply` drives it (D3-3). ──\r\n    let ready_path = home.path().join(\"brain.ready\");\r\n    let stop = Arc::new(AtomicBool::new(false));\r\n    let restart = Arc::new(BrainRestart::new());\r\n    let selected = Arc::new(Mutex::new(fixture_a.clone()));\r\n    let home_path = home.path().to_path_buf();\r\n\r\n    let sup = {\r\n        let stop = Arc::clone(&stop);\r\n        let restart = Arc::clone(&restart);\r\n        let selected = Arc::clone(&selected);\r\n        thread::spawn(move || {\r\n            supervise_brain(\r\n                &stop,\r\n                &restart,\r\n                Duration::from_millis(50),\r\n                &NoTrialEnv,\r\n                Duration::from_secs(30),\r\n                move |gen, reason: StartReason, _binary| {\r\n                    let exe = selected.lock().unwrap().clone();\r\n                    Command::new(&exe)\r\n                        .no_window()\r\n                        .args([\r\n                            \"daemon\",\r\n                            \"brain\",\r\n                            \"--generation\",\r\n                            &gen.to_string(),\r\n                            \"--start-reason\",\r\n                            reason.as_arg(),\r\n                        ])\r\n                        .env(\"SPT_HOME\", &home_path)\r\n                        .spawn()\r\n                },\r\n            );\r\n        })\r\n    };\r\n\r\n    // ── Generation 0: the real child came up from fixture A, resumed the hosted\r\n    //    session (the first N≥1 cursor-of-record resume), and stamped exe_hash A. ──\r\n    let (pid0, _gen0, hash0) = wait_ready(&ready_path, None, Duration::from_secs(45))\r\n        .unwrap_or_else(|| teardown_panic(&stop, \"gen-0 brain never signalled ready\"));\r\n    assert_eq!(\r\n        hash0.as_deref(),\r\n        Some(hash_a.as_str()),\r\n        \"gen-0 brain.ready exe_hash must be fixture A\"\r\n    );\r\n    assert_eq!(broker.session_count(), 1, \"session survived the driver drop + resume\");\r\n    assert_eq!(\r\n        broker.session_pid(sid),\r\n        Some(child_pid),\r\n        \"the PTY child's pid is unchanged through the resume\"\r\n    );\r\n    assert_eq!(broker.net_conn_count(), 1, \"the QUIC conn survived the driver drop\");\r\n\r\n    // ── SWAP: flip the selected binary to B, then trigger a planned restart\r\n    //    (what `apply` does: swap on disk, signal the brain to cycle). ──\r\n    *selected.lock().unwrap() = fixture_b.clone();\r\n    restart.request();\r\n\r\n    // ── Generation 1: the supervisor respawned the brain PROCESS onto the\r\n    //    SWAPPED binary. New pid, exe_hash == B != A (NEW BYTES RAN). ──\r\n    let (pid1, _gen1, hash1) = wait_ready(&ready_path, Some(pid0), Duration::from_secs(45))\r\n        .unwrap_or_else(|| teardown_panic(&stop, \"gen-1 brain never respawned onto the swap\"));\r\n    assert_ne!(pid0, pid1, \"the respawned brain is a fresh process\");\r\n    assert_eq!(\r\n        hash1.as_deref(),\r\n        Some(hash_b.as_str()),\r\n        \"post-swap exe_hash must be fixture B — the supervisor ran the NEW bytes, \\\r\n         not a relaunch of the old (the property brain_swap.rs cannot prove)\"\r\n    );\r\n    assert_ne!(\r\n        hash1.as_deref(),\r\n        Some(hash_a.as_str()),\r\n        \"post-swap exe_hash must differ from the pre-swap binary\"\r\n    );\r\n\r\n    // ── THE SURVIVAL INVARIANT: both broker-held endpoints are untouched across\r\n    //    the brain-PROCESS swap. ──\r\n    assert_eq!(\r\n        broker.session_pid(sid),\r\n        Some(child_pid),\r\n        \"REQ-UPD-3: the hosted PTY child's pid is unchanged across the brain-process swap\"\r\n    );\r\n    assert_eq!(broker.session_count(), 1, \"exactly one hosted session throughout\");\r\n    assert_eq!(\r\n        broker.net_conn_count(),\r\n        1,\r\n        \"the live QUIC conn the broker holds survived the brain-process swap (SPIKE-03 in production topology)\"\r\n    );\r\n\r\n    // ── FUNCTIONAL survival (doyle F1, plan choreography 5(d)): \"held\" is not\r\n    //    \"alive\". Drive a marker through the held PTY child POST-swap and watch the\r\n    //    broker-side output seq advance — proving the full round-trip (input →\r\n    //    child → echo → broker ring) works through the swapped-in brain, not that\r\n    //    a zombie handle merely lingers. The input rides the KIND_INPUT effect verb\r\n    //    on a FRESH raw connection, which does NOT steal the supervised brain's\r\n    //    output subscription (it is not an attach). ──\r\n    let seq_before = broker\r\n        .session_output_seq(sid)\r\n        .unwrap_or_else(|| teardown_panic(&stop, \"hosted session vanished before the functional probe\"));\r\n    {\r\n        let mut raw = LocalSocketTransport::connect(&broker_socket_name())\r\n            .unwrap_or_else(|_| teardown_panic(&stop, \"could not open a raw input connection\"));\r\n        send_hello(&mut raw, Role::Brain)\r\n            .unwrap_or_else(|_| teardown_panic(&stop, \"raw hello failed\"));\r\n        let input = InputReq {\r\n            session_id: sid,\r\n            data_b64: encode_bytes(b\"D7-1 POST-SWAP FUNCTIONAL PROBE\\r\"),\r\n            op_id: None,\r\n            minter: None,\r\n            ack: true,\r\n        };\r\n        write_frame(\r\n            &mut raw,\r\n            &Envelope::new(KIND_INPUT, serde_json::to_value(input).expect(\"InputReq serializes\")),\r\n        )\r\n        .unwrap_or_else(|_| teardown_panic(&stop, \"could not write the marker input frame\"));\r\n    }\r\n    // The echo child (cat/findstr) echoes the marker → output appended → next_seq\r\n    // advances. Poll the broker's own log high-water.\r\n    let mut advanced = false;\r\n    let deadline = Instant::now() + Duration::from_secs(15);\r\n    while Instant::now() < deadline {\r\n        if broker.session_output_seq(sid).map(|s| s > seq_before).unwrap_or(false) {\r\n            advanced = true;\r\n            break;\r\n        }\r\n        thread::sleep(Duration::from_millis(50));\r\n    }\r","truncated":true,"truncatedBy":"lines","totalLines":226,"totalBytes":9807,"outputLines":205,"outputBytes":9807,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn file_hash(path: &std::path::Path) -> String {\n…\n    let mut h = Sha256::new();\r\n    h.update(&bytes);\r\n    let digest = h.finalize();\r\n    let mut s = String::with_capacity(digest.len() * 2);\r\n    for b in digest {\r\n        s.push_str(&format!(\"{b:02x}\"));\r\n    }\r\n    s\r\n}\r\n\r\n#[test]\r\nfn pty_and_quic_survive_brain_process_restart_onto_swapped_binary() {\r\n    // The in-process broker + brain children both resolve `broker_socket_name()`\r\n    // from SPT_HOME, so the supervised child connects to OUR broker. (The\r\n    // established daemon-test pattern — daemon_e2e.rs et al. set it the same way.)\r\n    let home = tempfile::tempdir().expect(\"tempdir\");\r\n    // Single-test binary by design — this is the SOLE `#[test]` here, so it owns\r\n    // the process-global SPT_HOME on its own thread (cross-binary tests are\r\n    // separate processes). A SECOND `#[test]` added to this file MUST bring the\r\n    // env-lock discipline (a shared mutex / serialized home), or it will race this\r\n    // one — the D6 env-race lesson (see the spt-daemon test_home pattern).\r\n    std::env::set_var(\"SPT_HOME\", home.path());\r\n\r\n    // ── Two fixtures: A, and B = A + appended padding (the on-disk \"swap\"). ──\r\n    // Both are the real `spt` binary, so `daemon brain` runs; B differs only by\r\n    // trailing bytes (behavior identical, exe_hash flips).\r\n    let spt_bin = PathBuf::from(env!(\"CARGO_BIN_EXE_spt\"));\r\n    let suffix = spt_bin.extension().map(|e| format!(\".{}\", e.to_string_lossy())).unwrap_or_default();\r\n    let fixture_a = home.path().join(format!(\"brain-A{suffix}\"));\r\n    let fixture_b = home.path().join(format!(\"brain-B{suffix}\"));\r\n    std::fs::copy(&spt_bin, &fixture_a).expect(\"stage fixture A\");\r\n    std::fs::copy(&spt_bin, &fixture_b).expect(\"stage fixture B\");\r\n    {\r\n        use std::io::Write;\r\n        let mut f = std::fs::OpenOptions::new()\r\n            .append(true)\r\n            .open(&fixture_b)\r\n            .expect(\"open fixture B to append padding\");\r\n        f.write_all(&[0u8; 4096]).expect(\"append padding to fixture B\");\r\n    }\r\n    let hash_a = file_hash(&fixture_a);\r\n    let hash_b = file_hash(&fixture_b);\r\n    assert_ne!(hash_a, hash_b, \"the swap must flip the binary hash\");\r\n\r\n    // ── The broker (leg-b stable kernel) + a loopback QUIC peer. ──\r\n    let broker = net_broker(&broker_socket_name(), Identity::generate(), &home.path().join(\"a\"));\r\n    let peer_name = format!(\"spt-d7-peer-{}.sock\", std::process::id());\r\n    let _peer = net_broker(&peer_name, Identity::generate(), &home.path().join(\"b\"));\r\n\r\n    // ── Establish the two endpoints ONCE via a test-driver brain, then drop it\r\n    //    HARD (the D4 hard-kill posture — the outgoing side is gone before the\r\n    //    supervised child resumes). ──\r\n    let (sid, child_pid) = {\r\n        let mut driver = connect_retry(&broker_socket_name());\r\n\r\n        // (1) A hosted PTY child the broker holds.\r\n        let sid = driver.spawn_session(echo_spawn_req()).expect(\"spawn session\");\r\n        let child_pid = broker.session_pid(sid).expect(\"hosted child has a pid\");\r\n\r\n        // (2) A live QUIC connection the broker holds: dial the loopback peer.\r\n        let peer_addr = {\r\n            let mut peer_brain = connect_retry(&peer_name);\r\n            peer_brain.net_status().expect(\"peer status\").addr\r\n        };\r\n        driver.net_dial(peer_addr, None).expect(\"dial the loopback peer\");\r\n\r\n        // Both endpoints are now broker-held.\r\n        assert_eq!(broker.session_count(), 1, \"one hosted session established\");\r\n        assert_eq!(broker.net_conn_count(), 1, \"one live QUIC conn established\");\r\n        (sid, child_pid)\r\n        // driver dropped here — HARD detach (no graceful handoff frame).\r\n    };\r\n\r\n    // ── Start the production supervisor, spawning the REAL `spt daemon brain`\r\n    //    child from the currently-selected fixture (A). NoTrialEnv = the\r\n    //    pre-D6 respawn loop (no readiness gate); the swap is driven by the\r\n    //    planned-restart signal, exactly as a real `apply` drives it (D3-3). ──\r\n    let ready_path = home.path().join(\"brain.ready\");\r\n    let stop = Arc::new(AtomicBool::new(false));\r\n    let restart = Arc::new(BrainRestart::new());\r\n    let selected = Arc::new(Mutex::new(fixture_a.clone()));\r\n    let home_path = home.path().to_path_buf();\r\n\r\n    let sup = {\r\n        let stop = Arc::clone(&stop);\r\n        let restart = Arc::clone(&restart);\r\n        let selected = Arc::clone(&selected);\r\n        thread::spawn(move || {\r\n            supervise_brain(\r\n                &stop,\r\n                &restart,\r\n                Duration::from_millis(50),\r\n                &NoTrialEnv,\r\n                Duration::from_secs(30),\r\n                move |gen, reason: StartReason, _binary| {\r\n                    let exe = selected.lock().unwrap().clone();\r\n                    Command::new(&exe)\r\n                        .no_window()\r\n                        .args([\r\n                            \"daemon\",\r\n                            \"brain\",\r\n                            \"--generation\",\r\n                            &gen.to_string(),\r\n                            \"--start-reason\",\r\n                            reason.as_arg(),\r\n                        ])\r\n                        .env(\"SPT_HOME\", &home_path)\r\n                        .spawn()\r\n                },\r\n            );\r\n        })\r\n    };\r\n\r\n    // ── Generation 0: the real child came up from fixture A, resumed the hosted\r\n    //    session (the first N≥1 cursor-of-record resume), and stamped exe_hash A. ──\r\n    let (pid0, _gen0, hash0) = wait_ready(&ready_path, None, Duration::from_secs(45))\r\n        .unwrap_or_else(|| teardown_panic(&stop, \"gen-0 brain never signalled ready\"));\r\n    assert_eq!(\r\n        hash0.as_deref(),\r\n        Some(hash_a.as_str()),\r\n        \"gen-0 brain.ready exe_hash must be fixture A\"\r\n    );\r\n    assert_eq!(broker.session_count(), 1, \"session survived the driver drop + resume\");\r\n    assert_eq!(\r\n        broker.session_pid(sid),\r\n        Some(child_pid),\r\n        \"the PTY child's pid is unchanged through the resume\"\r\n    );\r\n    assert_eq!(broker.net_conn_count(), 1, \"the QUIC conn survived the driver drop\");\r\n\r\n    // ── SWAP: flip the selected binary to B, then trigger a planned restart\r\n    //    (what `apply` does: swap on disk, signal the brain to cycle). ──\r\n    *selected.lock().unwrap() = fixture_b.clone();\r\n    restart.request();\r\n\r\n    // ── Generation 1: the supervisor respawned the brain PROCESS onto the\r\n    //    SWAPPED binary. New pid, exe_hash == B != A (NEW BYTES RAN). ──\r\n    let (pid1, _gen1, hash1) = wait_ready(&ready_path, Some(pid0), Duration::from_secs(45))\r\n        .unwrap_or_else(|| teardown_panic(&stop, \"gen-1 brain never respawned onto the swap\"));\r\n    assert_ne!(pid0, pid1, \"the respawned brain is a fresh process\");\r\n    assert_eq!(\r\n        hash1.as_deref(),\r\n        Some(hash_b.as_str()),\r\n        \"post-swap exe_hash must be fixture B — the supervisor ran the NEW bytes, \\\r\n         not a relaunch of the old (the property brain_swap.rs cannot prove)\"\r\n    );\r\n    assert_ne!(\r\n        hash1.as_deref(),\r\n        Some(hash_a.as_str()),\r\n        \"post-swap exe_hash must differ from the pre-swap binary\"\r\n    );\r\n\r\n    // ── THE SURVIVAL INVARIANT: both broker-held endpoints are untouched across\r\n    //    the brain-PROCESS swap. ──\r\n    assert_eq!(\r\n        broker.session_pid(sid),\r\n        Some(child_pid),\r\n        \"REQ-UPD-3: the hosted PTY child's pid is unchanged across the brain-process swap\"\r\n    );\r\n    assert_eq!(broker.session_count(), 1, \"exactly one hosted session throughout\");\r\n    assert_eq!(\r\n        broker.net_conn_count(),\r\n        1,\r\n        \"the live QUIC conn the broker holds survived the brain-process swap (SPIKE-03 in production topology)\"\r\n    );\r\n\r\n    // ── FUNCTIONAL survival (doyle F1, plan choreography 5(d)): \"held\" is not\r\n    //    \"alive\". Drive a marker through the held PTY child POST-swap and watch the\r\n    //    broker-side output seq advance — proving the full round-trip (input →\r\n    //    child → echo → broker ring) works through the swapped-in brain, not that\r\n    //    a zombie handle merely lingers. The input rides the KIND_INPUT effect verb\r\n    //    on a FRESH raw connection, which does NOT steal the supervised brain's\r\n    //    output subscription (it is not an attach). ──\r\n    let seq_before = broker\r\n        .session_output_seq(sid)\r\n        .unwrap_or_else(|| teardown_panic(&stop, \"hosted session vanished before the functional probe\"));\r\n    {\r\n        let mut raw = LocalSocketTransport::connect(&broker_socket_name())\r\n            .unwrap_or_else(|_| teardown_panic(&stop, \"could not open a raw input connection\"));\r\n        send_hello(&mut raw, Role::Brain)\r\n            .unwrap_or_else(|_| teardown_panic(&stop, \"raw hello failed\"));\r\n        let input = InputReq {\r\n            session_id: sid,\r\n            data_b64: encode_bytes(b\"D7-1 POST-SWAP FUNCTIONAL PROBE\\r\"),\r\n            op_id: None,\r\n            minter: None,\r\n            ack: true,\r\n        };\r\n        write_frame(\r\n            &mut raw,\r\n            &Envelope::new(KIND_INPUT, serde_json::to_value(input).expect(\"InputReq serializes\")),\r\n        )\r\n        .unwrap_or_else(|_| teardown_panic(&stop, \"could not write the marker input frame\"));\r\n    }\r\n    // The echo child (cat/findstr) echoes the marker → output appended → next_seq\r\n    // advances. Poll the broker's own log high-water.\r\n    let mut advanced = false;\r\n    let deadline = Instant::now() + Duration::from_secs(15);\r\n    while Instant::now() < deadline {\r\n        if broker.session_output_seq(sid).map(|s| s > seq_before).unwrap_or(false) {\r\n            advanced = true;\r\n            break;\r\n        }\r\n        thread::sleep(Duration::from_millis(50));\r\n    }\r\n…\n}","startLine":176,"lineNumbers":[176,null,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,null,396]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\tests\\brain_survive.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":404,"totalBytes":9807,"outputLines":205,"outputBytes":9807,"shownRange":{"start":179,"end":383},"nextOffset":384}}},"isError":false,"timestamp":1784247434384}}
{"type":"message","id":"12b6571e","parentId":"8952e2a1","timestamp":"2026-07-17T00:17:14.416Z","message":{"role":"toolResult","toolCallId":"call_tUq9UsuueGKMS47u1OaWz8ar|fc_099a7567dadef391016a59748a22e8819b9bf3710521e206b2","toolName":"read","content":[{"type":"text","text":"fn attach_viewport(\n…\n) -> Result<PumpEnd, String> {\n    loop {\n…\n\n        // Final ends (incl. hard errors) exit the loop to the parting copy.\n        let severed_kind = match &end {\n            Ok(e) if reconnect_class(e) == EndClass::Reconnect => match e {\n                PumpEnd::BrokerGone => \"the daemon connection dropped\",\n                _ => \"the connection was severed\",\n            },\n            _ => return end,\n        };\n\n        // Reconnect: full-screen centered banner (operator language — the internal\n        // detail stays off the terminal per the Q4 UX rule), then re-drive\n        // establish_attach inside the bounded window. A detach keypress mid-window\n        // aborts the retry loop honestly. The banner repaints each iteration with a\n        // live COUNTDOWN (REQ-RC-RECONNECT-TRUTH) so the operator sees rc is bounded\n        // and working, never the old apparent freeze at a static \"Reconnecting…\".\n        // establish_attach is CONNECT-ONLY: a down daemon yields DaemonDown here and\n        // is retried (the operator may bring it back) WITHOUT any WMI resurrection.\n        let target = remote_node.clone().unwrap_or_else(|| \"local daemon\".to_string());\n        let retry_started = Instant::now();\n        let reestablished = loop {\n            let elapsed = retry_started.elapsed();\n            if reconnect_expired(elapsed) {\n                break None;\n            }\n            let (cols, rows) = crossterm::terminal::size().unwrap_or((80, 24));\n            let remaining = reconnect_remaining_secs(elapsed);\n            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\n            let _ = out.flush();\n            // Operator gives up mid-reconnect: honor the detach keys.\n            if let Some(rx) = rx {\n                let mut detached = false;\n                while let Ok(msg) = rx.try_recv() {\n                    if matches!(msg, StdinMsg::Detach) {\n                        detached = true;\n                    }\n                }\n                if detached {\n                    break Some(Err(())); // detach sentinel\n                }\n            }\n            match establish_attach(endpoint_id, intent) {\n                Ok(next) => break Some(Ok(next)),\n                // Every fail shape retries until the window expires — the node/daemon\n                // may still be coming back. DaemonDown included: rc waits for the\n                // operator to restart it, it does NOT auto-start one.\n                Err(_) => std::thread::sleep(RECONNECT_PAUSE),\n            }\n        };\n        match reestablished {\n            Some(Ok(next)) => {\n                *remote_node = next.remote_node.clone();\n                est = next;\n                // Loop: fresh resize + pump. The re-serve replays the output ring,\n                // repainting the screen the banner cleared.\n            }\n            Some(Err(())) => return Ok(PumpEnd::Detached),\n            None => {\n                // Give-up copy selection (REQ-RC-RECONNECT-TRUTH): a still-down local\n                // daemon (only knowable for a LOCAL target) gets the loud \"session\n                // lost — daemon down\" copy; anything else keeps the generic\n                // didn't-reconnect copy. Remote severs never probe local daemon state.\n                let daemon_down =\n                    remote_node.is_none() && !spt_daemon::daemon::is_running();\n                return Ok(PumpEnd::ReconnectGaveUp {\n                    detail: severed_kind.to_string(),\n                    daemon_down,\n                });\n            }\n        }\n    }\n}\n\n/// How long the B-3 reconnect loop keeps re-driving `establish_attach` after a\n/// severed viewport before the plain-language give-up (REQ-RC-RECONNECT).\n/// Generous: a daemon bounce (`spt daemon stop` + the next auto-start) and a\n/// node's broker restart both land well inside it.\nconst RECONNECT_WINDOW: Duration = Duration::from_secs(30);\n/// The pause between reconnect attempts (each attempt already carries its own\n/// bounded setup deadlines — this just keeps the loop from hammering).\nconst RECONNECT_PAUSE: Duration = Duration::from_secs(1);\n\n/// The give-up decision, pulled out so it unit-tests without a 30s wait.\n// [impl->REQ-RC-RECONNECT]\nfn reconnect_expired(elapsed: Duration) -> bool {\n    elapsed >= RECONNECT_WINDOW\n}\n\n/// Seconds still left in the reconnect window, for the banner countdown\n/// (REQ-RC-RECONNECT-TRUTH). CEIL so the display reads a full `RECONNECT_WINDOW`s\n/// at the start and never flashes `0s` while the loop is still trying (0 only once\n/// the window is fully spent, where the caller has already broken to give-up).\n// [impl->REQ-RC-RECONNECT-TRUTH]\nfn reconnect_remaining_secs(elapsed: Duration) -> u64 {\n    let left = RECONNECT_WINDOW.saturating_sub(elapsed);\n    // ceil(left / 1s) via integer millis — 1..=WINDOW while any time remains, 0 at/past.\n    (left.as_millis() as u64).div_ceil(1000)\n}\n\n/// Which pump ends re-drive the viewport (B-3) vs. exit with parting copy.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum EndClass {\n    Final,\n    Reconnect,\n}\n\n[Showing lines 1559-1663 of 3214. Use :1664 to continue]"}],"details":{"truncation":{"content":"\r\n        // Final ends (incl. hard errors) exit the loop to the parting copy.\r\n        let severed_kind = match &end {\r\n            Ok(e) if reconnect_class(e) == EndClass::Reconnect => match e {\r\n                PumpEnd::BrokerGone => \"the daemon connection dropped\",\r\n                _ => \"the connection was severed\",\r\n            },\r\n            _ => return end,\r\n        };\r\n\r\n        // Reconnect: full-screen centered banner (operator language — the internal\r\n        // detail stays off the terminal per the Q4 UX rule), then re-drive\r\n        // establish_attach inside the bounded window. A detach keypress mid-window\r\n        // aborts the retry loop honestly. The banner repaints each iteration with a\r\n        // live COUNTDOWN (REQ-RC-RECONNECT-TRUTH) so the operator sees rc is bounded\r\n        // and working, never the old apparent freeze at a static \"Reconnecting…\".\r\n        // establish_attach is CONNECT-ONLY: a down daemon yields DaemonDown here and\r\n        // is retried (the operator may bring it back) WITHOUT any WMI resurrection.\r\n        let target = remote_node.clone().unwrap_or_else(|| \"local daemon\".to_string());\r\n        let retry_started = Instant::now();\r\n        let reestablished = loop {\r\n            let elapsed = retry_started.elapsed();\r\n            if reconnect_expired(elapsed) {\r\n                break None;\r\n            }\r\n            let (cols, rows) = crossterm::terminal::size().unwrap_or((80, 24));\r\n            let remaining = reconnect_remaining_secs(elapsed);\r\n            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\r\n            let _ = out.flush();\r\n            // Operator gives up mid-reconnect: honor the detach keys.\r\n            if let Some(rx) = rx {\r\n                let mut detached = false;\r\n                while let Ok(msg) = rx.try_recv() {\r\n                    if matches!(msg, StdinMsg::Detach) {\r\n                        detached = true;\r\n                    }\r\n                }\r\n                if detached {\r\n                    break Some(Err(())); // detach sentinel\r\n                }\r\n            }\r\n            match establish_attach(endpoint_id, intent) {\r\n                Ok(next) => break Some(Ok(next)),\r\n                // Every fail shape retries until the window expires — the node/daemon\r\n                // may still be coming back. DaemonDown included: rc waits for the\r\n                // operator to restart it, it does NOT auto-start one.\r\n                Err(_) => std::thread::sleep(RECONNECT_PAUSE),\r\n            }\r\n        };\r\n        match reestablished {\r\n            Some(Ok(next)) => {\r\n                *remote_node = next.remote_node.clone();\r\n                est = next;\r\n                // Loop: fresh resize + pump. The re-serve replays the output ring,\r\n                // repainting the screen the banner cleared.\r\n            }\r\n            Some(Err(())) => return Ok(PumpEnd::Detached),\r\n            None => {\r\n                // Give-up copy selection (REQ-RC-RECONNECT-TRUTH): a still-down local\r\n                // daemon (only knowable for a LOCAL target) gets the loud \"session\r\n                // lost — daemon down\" copy; anything else keeps the generic\r\n                // didn't-reconnect copy. Remote severs never probe local daemon state.\r\n                let daemon_down =\r\n                    remote_node.is_none() && !spt_daemon::daemon::is_running();\r\n                return Ok(PumpEnd::ReconnectGaveUp {\r\n                    detail: severed_kind.to_string(),\r\n                    daemon_down,\r\n                });\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// How long the B-3 reconnect loop keeps re-driving `establish_attach` after a\r\n/// severed viewport before the plain-language give-up (REQ-RC-RECONNECT).\r\n/// Generous: a daemon bounce (`spt daemon stop` + the next auto-start) and a\r\n/// node's broker restart both land well inside it.\r\nconst RECONNECT_WINDOW: Duration = Duration::from_secs(30);\r\n/// The pause between reconnect attempts (each attempt already carries its own\r\n/// bounded setup deadlines — this just keeps the loop from hammering).\r\nconst RECONNECT_PAUSE: Duration = Duration::from_secs(1);\r\n\r\n/// The give-up decision, pulled out so it unit-tests without a 30s wait.\r\n// [impl->REQ-RC-RECONNECT]\r\nfn reconnect_expired(elapsed: Duration) -> bool {\r\n    elapsed >= RECONNECT_WINDOW\r\n}\r\n\r\n/// Seconds still left in the reconnect window, for the banner countdown\r\n/// (REQ-RC-RECONNECT-TRUTH). CEIL so the display reads a full `RECONNECT_WINDOW`s\r\n/// at the start and never flashes `0s` while the loop is still trying (0 only once\r\n/// the window is fully spent, where the caller has already broken to give-up).\r\n// [impl->REQ-RC-RECONNECT-TRUTH]\r\nfn reconnect_remaining_secs(elapsed: Duration) -> u64 {\r\n    let left = RECONNECT_WINDOW.saturating_sub(elapsed);\r\n    // ceil(left / 1s) via integer millis — 1..=WINDOW while any time remains, 0 at/past.\r\n    (left.as_millis() as u64).div_ceil(1000)\r\n}\r\n\r\n/// Which pump ends re-drive the viewport (B-3) vs. exit with parting copy.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum EndClass {\r\n    Final,\r\n    Reconnect,\r\n}\r","truncated":true,"truncatedBy":"lines","totalLines":1656,"totalBytes":5169,"outputLines":105,"outputBytes":5169,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn attach_viewport(\n…\n) -> Result<PumpEnd, String> {\n    loop {\n…\n\r\n        // Final ends (incl. hard errors) exit the loop to the parting copy.\r\n        let severed_kind = match &end {\r\n            Ok(e) if reconnect_class(e) == EndClass::Reconnect => match e {\r\n                PumpEnd::BrokerGone => \"the daemon connection dropped\",\r\n                _ => \"the connection was severed\",\r\n            },\r\n            _ => return end,\r\n        };\r\n\r\n        // Reconnect: full-screen centered banner (operator language — the internal\r\n        // detail stays off the terminal per the Q4 UX rule), then re-drive\r\n        // establish_attach inside the bounded window. A detach keypress mid-window\r\n        // aborts the retry loop honestly. The banner repaints each iteration with a\r\n        // live COUNTDOWN (REQ-RC-RECONNECT-TRUTH) so the operator sees rc is bounded\r\n        // and working, never the old apparent freeze at a static \"Reconnecting…\".\r\n        // establish_attach is CONNECT-ONLY: a down daemon yields DaemonDown here and\r\n        // is retried (the operator may bring it back) WITHOUT any WMI resurrection.\r\n        let target = remote_node.clone().unwrap_or_else(|| \"local daemon\".to_string());\r\n        let retry_started = Instant::now();\r\n        let reestablished = loop {\r\n            let elapsed = retry_started.elapsed();\r\n            if reconnect_expired(elapsed) {\r\n                break None;\r\n            }\r\n            let (cols, rows) = crossterm::terminal::size().unwrap_or((80, 24));\r\n            let remaining = reconnect_remaining_secs(elapsed);\r\n            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\r\n            let _ = out.flush();\r\n            // Operator gives up mid-reconnect: honor the detach keys.\r\n            if let Some(rx) = rx {\r\n                let mut detached = false;\r\n                while let Ok(msg) = rx.try_recv() {\r\n                    if matches!(msg, StdinMsg::Detach) {\r\n                        detached = true;\r\n                    }\r\n                }\r\n                if detached {\r\n                    break Some(Err(())); // detach sentinel\r\n                }\r\n            }\r\n            match establish_attach(endpoint_id, intent) {\r\n                Ok(next) => break Some(Ok(next)),\r\n                // Every fail shape retries until the window expires — the node/daemon\r\n                // may still be coming back. DaemonDown included: rc waits for the\r\n                // operator to restart it, it does NOT auto-start one.\r\n                Err(_) => std::thread::sleep(RECONNECT_PAUSE),\r\n            }\r\n        };\r\n        match reestablished {\r\n            Some(Ok(next)) => {\r\n                *remote_node = next.remote_node.clone();\r\n                est = next;\r\n                // Loop: fresh resize + pump. The re-serve replays the output ring,\r\n                // repainting the screen the banner cleared.\r\n            }\r\n            Some(Err(())) => return Ok(PumpEnd::Detached),\r\n            None => {\r\n                // Give-up copy selection (REQ-RC-RECONNECT-TRUTH): a still-down local\r\n                // daemon (only knowable for a LOCAL target) gets the loud \"session\r\n                // lost — daemon down\" copy; anything else keeps the generic\r\n                // didn't-reconnect copy. Remote severs never probe local daemon state.\r\n                let daemon_down =\r\n                    remote_node.is_none() && !spt_daemon::daemon::is_running();\r\n                return Ok(PumpEnd::ReconnectGaveUp {\r\n                    detail: severed_kind.to_string(),\r\n                    daemon_down,\r\n                });\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// How long the B-3 reconnect loop keeps re-driving `establish_attach` after a\r\n/// severed viewport before the plain-language give-up (REQ-RC-RECONNECT).\r\n/// Generous: a daemon bounce (`spt daemon stop` + the next auto-start) and a\r\n/// node's broker restart both land well inside it.\r\nconst RECONNECT_WINDOW: Duration = Duration::from_secs(30);\r\n/// The pause between reconnect attempts (each attempt already carries its own\r\n/// bounded setup deadlines — this just keeps the loop from hammering).\r\nconst RECONNECT_PAUSE: Duration = Duration::from_secs(1);\r\n\r\n/// The give-up decision, pulled out so it unit-tests without a 30s wait.\r\n// [impl->REQ-RC-RECONNECT]\r\nfn reconnect_expired(elapsed: Duration) -> bool {\r\n    elapsed >= RECONNECT_WINDOW\r\n}\r\n\r\n/// Seconds still left in the reconnect window, for the banner countdown\r\n/// (REQ-RC-RECONNECT-TRUTH). CEIL so the display reads a full `RECONNECT_WINDOW`s\r\n/// at the start and never flashes `0s` while the loop is still trying (0 only once\r\n/// the window is fully spent, where the caller has already broken to give-up).\r\n// [impl->REQ-RC-RECONNECT-TRUTH]\r\nfn reconnect_remaining_secs(elapsed: Duration) -> u64 {\r\n    let left = RECONNECT_WINDOW.saturating_sub(elapsed);\r\n    // ceil(left / 1s) via integer millis — 1..=WINDOW while any time remains, 0 at/past.\r\n    (left.as_millis() as u64).div_ceil(1000)\r\n}\r\n\r\n/// Which pump ends re-drive the viewport (B-3) vs. exit with parting copy.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum EndClass {\r\n    Final,\r\n    Reconnect,\r\n}\r","startLine":1519,"lineNumbers":[1519,null,1529,1530,null,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\rc.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":3214,"totalBytes":5169,"outputLines":105,"outputBytes":5169,"shownRange":{"start":1559,"end":1663},"nextOffset":1664}}},"isError":false,"timestamp":1784247434415}}
{"type":"message","id":"8bb6d61d","parentId":"12b6571e","timestamp":"2026-07-17T00:17:39.430Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing rc/view transport stall timing**\n\n**Investigating 30-second reconnect effect on restarts**\n\n**Confirming release tag and changelog metadata**"},{"type":"thinking","thinking":"**Preparing release notes clarification**"},{"type":"toolCall","id":"call_oQta4ZNPTcyFgaVJ7ujenw8i|fc_099a7567dadef391016a59749d4968819b8caf1162d716b7f5","name":"irc","arguments":{"i":"Sharing release contract findings","op":"send","to":"Main","message":"Release-contract findings: v0.34 tag refs/tags/v0.34.0 = b7b074c; release branch commit eb9fd864 ('release: v0.34.0 counter61'), W1 impl commits ad7fde8/2a110ee/8425357 + ADR pin 571d6a5. Contract mismatch: v0.34 CHANGELOG names `spt update --restart` as nondisturbing, but cli.rs 5054-56/5070ff and REQ-UPDATE-RESTART-SAFE-SWAP explicitly call Finish lethal: full daemon/broker/PTY restart; v0.32 notes correctly say sessions restart. rc.rs has explicit 30s RECONNECT_WINDOW, a plausible 20-30s post-restart recovery window, not a ConPTY child stall. Also routine v33→v34 apply preserves OLD v33 broker; v34 opener/retire verbs fall back/best-effort, so durable opener + physical retirement unavailable until broker full-cycle. No mixed v33 broker/v34 redispatch stream test (n1_pairing only idle heartbeats, zero stream rows). More final soon.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":33663,"output":857,"cacheRead":169472,"cacheWrite":0,"totalTokens":203992,"reasoningTokens":560,"cost":{"input":0.16831500000000002,"output":0.02571,"cacheRead":0.08473599999999999,"cacheWrite":0,"total":0.27876100000000004}},"stopReason":"toolUse","timestamp":1784247434458,"responseId":"resp_099a7567dadef391016a59748aa5ac819b8ee0d09682349c93","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a59748fb148819b82af8647e9c19b37","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXScDjogcZXGxlkGlbwFPhTJ86OqCfeGjVSwgLbhYOYXh8AXRqAl_0yE3DPTQ8k0BR3S7AWcfxS4Q9iEZVpDdqNbwAcD_utTBPL1eVp5pkTEFVVACJBVJgibRG9D4Yg3l1sKQp8B5tjShkWlOIg7gEu2zOMCe5ms-0HjcyWaxKg01NT0YSX_ZKjkyW_96sm_IBwNUJl6NUM-oImKPmtA8qWO4Evzo0yB6KqH3bM4SyeoSrTSZXLx560X8mbgq0LXUbtsviv2AaFwTkFrgpWH6DdSImzOqyvFHMxA8uBCzG8zadxGx9PwYZlEAOF7hl0Rb0Nxk188nv_1y-lSxtbC8urEbAj3Ltu8H0ZCLU7w9jnlP-QgnCUkCR4H7WyzcVnRZnh9dYMeuAYLdLyZMLBkUeGyPqcoF3jQj1dM-3oaH_YiZqkfek5AkH1who7exAOu2cwOgCrQShQ6W4Or2S83jaEPocgG9pAYvH3Ftd25yc3juBsBZiSvWGZpAcp8mx94VJNYGG0F1f7AbFyV4sNuXcmVEja0KpqPbUyVU0XWLPxYbeqjyPlVD1hNUvZ6A-bw3N0k-dJpCB6twjySeKmcwpKgGawPyVKXEScR2H_NYeoJsKjec4bM8pq11ZBtVz-WS28pJYM4fJdJUZf2ArkHPYnL9HdQjgr2-Xwfo-ypA3Er0q85Wt2RA1maZFzW_rWvffSIFeH0hYvkqN74FeFYtn7CwYKNX_QGhDSLWqnjpjihd03ZFs-CJERPS2R8iP9tmstLi5LaQnI6fxBC-Ue1vSwKMDwpttByptavibfkGpISlj88jqFaYPvzllNX1o3ZCfPFtR4JgNDSsjz9J3HQS0KFp4l4z4KR8gUNDge5y5bx3iCmW6Wl1AcyrUlKMHYTfN9v1O_wbdCXEuFl7OoiVms4gTdOyUzi-3vVhFegnqXMWJgAaz_vu4lUHWIQZq1nDTBW-uN1lWzYBWsQNwJ0Lm4D5mj2Io0cYuV8QQ0cD3flDdNTQWcT4lRMEvKdkNCcnNUsiC5UBt76nGXtt-ZfuXqZGwqhbGg367ysTbatBLIbDR_7ZucuaVlHtMvVJpSndoJ8uqhTnDfwAV8fMJkTyxEQ_No7jnkFGPgkX-7-0C6rO3bvIzXb---_M01ZFR26YGzlvh1ojIEyas-CRTuwa2REHJPXiP2h3hwA4o1cEv2xXh_0dAHiicP8Rl04BNI8AiJWcMSR3_NYrHkBDB3chOtma_gFXJvQSzb2QsGBGNw31vUFVdztQFeR3BppNcwo2IskesBJWY4TRt2grdURHqyqRip7dnpjq1zrrkogG-RWo952G9mYzdNCCEUtd0HorEwkZybZQTTh_1vpQRGZsA1HBuUC7ky_N3nHatKyficXzqup3-380mJv5lWcK4IFVqSUunw7DUx_fK7Wcitml1C8SDUldzBlEzMqpUo9062Xujl-_5GOVl_NlxXHw1cIbLA2A1aVmRuJya7-l_MaPcx56iRVyP9vC9iqHzoIN1TqdDKYF_yV-fW9gZQfowqFt7BySIfxWleL83Ukm33W-pRkVWRw1kuk955EUaHNNeSyhjxt73cjuPOu1h4p77_iCO_F6gZHEuhMaeC2n4N03_sduA3q5PvWkf4iaFCp9aFegOSZJ2fZMfA7S1ixa5m-Eu5SPMBb0jWhXvd-TEsAaM0WwNGJNKTDvirpaQ-f_Hn-BIUJR33FA9ah3Z2FJ4a4bsItt8N6RdfdSJjbfgEB1Lcd55h5JMMD0C2Nv-hbr1Rn6Zf7pisdvf2uxMCd9xokOYd38HCKKYa19KPNB-u3TSirTf4fXzSOTliDe9fP198pr5hVelNnOTVCyGcJ1daVxPMVgeABPFo6x4bA45XvXV9vRjDq0JCthwn7j3OHaWUmAzxmMscwbuhOS8YjjnYkXPjKR4goji-cT34NXpRB2FTc1sgodAd-ZNrt4ev9SnUV4ByFD3TBYnFocq9zvOmcGBBovNtDFKQVN58zsfXXDbnCbZfdDN4k5Lsw1zHWk4iKQusO3qh-f68bD3AfegBUooTe3TGQffN-X6R-h7pWWCp_rW3gBrw-Qwjcftzw_29D60FiND9FP-oJ1mlvmKyZ8jCL6-hRJo2ehNyweKtv4WBriTWbvTW4T2Hc1uIWAQT2bFFUBzBqgPqybo38OiW8wJxP7XqSKgAZr9229KxUa7RJxzEFoopV7UMbTxhxzDqEXqJp68i_xbh9TmTTC2zBfGLQTBKczy_Ezzs7gXzFSnOi6dvq6VDDco5wF_WYPA6_1W6CN0_bF77rX5UUpRGghIGYtlLw4LLY2Oaxd8H-bKcLaPzewf-EgtQamV61V3LBMyWiOatnvIEcREUxUGshTqiHepc0LwZlgm2pfxw9j2dgksoODRPcU6B1jfVshrM3OgRg5HDyTYtw5AEJQYnq2zb1IwXiLsulADV5pjGW0ry6TT_sG-91NUcZ466q_8ylYdriNrfh86WPCuEAFC30UX6ZztyyYBo-3oMhxE1q4Mr6C0qMyrmP2bHyOY00p-KUfI_QWnQ4s4uPCTkwozoAfgyOGiHnDQheVerkx9V1ElADz3BgLWhOHhS2KzkK4DzmaC65FS3THPQ6x6sNMwfBDizedLRii5j_j7jyx9cHt_mOnP8_SV2e2g2g6z-Sdr-LH0dG7F6LFhMDI7OOBY25yelJYwR_Ti5A-6DmWgTNGz7Hk4UupgCgtsyqssorsNZrdPbnXbTn0Frsv7pkr82hg4pKpuTw8XbNWPqJ21c3UPwUhcg2U3jJt3f9KQTyhTXXyZyObO_EFBrKm_YZdjZnfkdf0yrQlorij2RYAbub8Jv7x-_wCX_NO4Ut4EF89sT_t_L_DGSpSace4nxWRcPl7v4nkYWHFrBWUddgANYEETugq5zMd4KEdsyoDQfN8AI-Prih9ISfkPYWP9FmPcID7GLHoP15rgYdrvOzEtyRAHBJ3IkgKpgVSatWfNuF-0Z5UJtJ6Q06Ti5XSQmpYi6nc5OdyQKN8VfyH7aOFqXhC09zvBLowYDvWQLioxHzDYl0WhUJUbIEBeaALepA3f0_AzYupMudTQnQLCn65kA1xuBOmUE3MSSXUttA9pPgILLq0yV-68SzhZS0mS6CS-VITQBQH1h3stAIZlsNfXEZXN0ZZKoZAZmUFo8lYzSJuY90IQUeJiOZIX2yxMH6h6F0LBo8CZ4lo8GlWkM1tHuk8TNvGm7NcuOyeJoTr6vZqXE39JweHsi9zK6pe9Z8oD0ojFCEfFcVmhXRwGuHkQh_UcUXV2NosGEBC0swlrVhxLY4IG9JUiWeoZnylVFptoBVcHLnEQnQgoapYkha8IX1VFnkFF-yOV4jdyN_1hFf22ZB9EyNpt8x1AiHsmpXoyLCt7sBQbYcbiEUDqArBWnwB09R9CVaYRQo5bOOSWKpWwYNAmeHGp6yxa2J8MHHWKQAuHNgZ8EzvshuQ-ZzfyHJ3DfyAZACdW2TzKO-ZfxVHllJF_4SO7lKJpQGq9I0aqIifRrZUirPNFkS0SKq865IIvkFkhRxhhEpWxB3b2ElLBQSYvelnUN9OP6leJUj7PwhUmtKs5RSoqokIWYGOXBKM5dlzC6bP0vqNnDIpUDRH10kz76mVZv5zPnPHteBDH-HGlLR8qkKSyndeM4AEOnBFvgghVCqaBWLFi99OVmQlJHPNATshj00VbtMcuQ6vOnMwG1ooCsX_WJ8R6joWX61kYTCnk-qTZU3NjkQ42i4cQ0nFYZirZvMuaz5fxlHvJQDHnOd5szzfM3krnrxcc1CVuNf30QqgCX_5nzxAFyqO2zAIYS6ScMlUgov11qL1idV8ESXJMw9p8TCipIYocg3nDpCVTASamVSSh8CvkioROeSBgPvctT6JuWw5MLFKi5ktr5dxzMdR1l6Oh_nLqh6S-yEJIQ4kzE893gl0RNZUi9Z1rk0iH8tA7OvaFL7vdQjZi95mfY5Y0yK0oNpUW-lNc85qggKPqQxMwrv1rnVr0Q-xP2YpBOkGXnpEvGHkfJE2mcm","summary":[{"type":"summary_text","text":"**Analyzing rc/view transport stall timing**"},{"type":"summary_text","text":"**Investigating 30-second reconnect effect on restarts**"},{"type":"summary_text","text":"**Confirming release tag and changelog metadata**"}]},{"id":"rs_099a7567dadef391016a59749c763c819bb4007adb4360c72a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXSd6aooK0TF4qPKbqYVy-qhNA662IxTlTvBkkN5CeMA4Zu9gsythwhzb_FmegyI4d9je-G7U4hZqZbBxFVd2t69rqVFgLB3vX1eY1NRIDC5zi3EMAWw5G41fnk3zcm8amfz-ruVYgXcUMeRDCLgZ4bsmuVhLgynk21ceCv-0_XbGZozEc9CbPEytnqdOCylFmvwYErN_ZUOdLvPItge8yaUvCTHHGOTlHiP1vFeKl0-nPiUBL9Hm_03KySAbYUVd5U2DbT3U846dBhzQdVZKKsd2JAdBZqL_3eSE2bP_bQ87m848iZzaL0S5hhQX_8YEpUn6uqiHVQWBLVGMDm53HBx6dqwhfQnKe5iF2SkaE0dxnsZQGihYd4clWBGhVdObS0hcITD8qpnGE5Iy2OLbxmBze7Hh9sbUa4QldiPcD-TQU8AtptNX3x2nYpUZWNnrHF0GGXVd_XIBEnv55_p-g4qbI7ybu987jfXo4NloOUwgQ383xKkfhW0bZcTyHooDIBYaYsTI3Vs2qoB1wrhSvdSwXEs8JgbP5fKh-EViGR8O21KKeRTqK2taH2ZpRUeXzMU3yykX4nmIsG-Y2WwV7t1mMbbU_vGCDCkWT7kSPnhSycxWw1BQEi9iVVsgJhj2JGxd2Jg0jU4Bw78T4jtSHoFwJgANzQBO3O1_PbNHSDIIDfEXSZlTmBjG5oVHbpXjgsweq5oZyUEaB-g-VcqyJzWcEgYFEsOUNLMrUXLdJsvOb0qlI75VhXibWF7voNKrzieFTzG7D4ksIw6QqzN3wYhjgtWneAOPngUQ8O1HSCiDja4v_jbF_c38vkDbrr1L5gEGaYIqiQiKzMSB3ZJ055OigLWE_KlLPyOh434voJmwnv3f7Uy67s67i8buV7VCLHC5LZz5I1akRF9Q8sqQpRLio8Pk86SIBgbgbuy_psb8I4OpHSZiAEnoNRHLaUbkTPu9sEBYCv6drIcia430uORBgWYqf7adMGl9R2oM-7MwQtmpfHMctSgogr3tzrkNvShsciovJFcg7U4cdwEJ6PcTGEZoku8MA0jhg-99ChqPpCF_CjZR3TksUag1uBIfG0M68MLxdQWVtTiD3G326NiIYZsz64MDtJn82A9aRVG3S9Dawan5ygrOQ7svWuq2lnsX1z8QABnKeH5IYzAWAnQNw==","summary":[{"type":"summary_text","text":"**Preparing release notes clarification**"}]},{"id":"fc_099a7567dadef391016a59749d4968819b8caf1162d716b7f5","type":"function_call","status":"completed","arguments":"{\"i\":\"Sharing release contract findings\",\"op\":\"send\",\"to\":\"Main\",\"message\":\"Release-contract findings: v0.34 tag refs/tags/v0.34.0 = b7b074c; release branch commit eb9fd864 ('release: v0.34.0 counter61'), W1 impl commits ad7fde8/2a110ee/8425357 + ADR pin 571d6a5. Contract mismatch: v0.34 CHANGELOG names `spt update --restart` as nondisturbing, but cli.rs 5054-56/5070ff and REQ-UPDATE-RESTART-SAFE-SWAP explicitly call Finish lethal: full daemon/broker/PTY restart; v0.32 notes correctly say sessions restart. rc.rs has explicit 30s RECONNECT_WINDOW, a plausible 20-30s post-restart recovery window, not a ConPTY child stall. Also routine v33→v34 apply preserves OLD v33 broker; v34 opener/retire verbs fall back/best-effort, so durable opener + physical retirement unavailable until broker full-cycle. No mixed v33 broker/v34 redispatch stream test (n1_pairing only idle heartbeats, zero stream rows). More final soon.\",\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_oQta4ZNPTcyFgaVJ7ujenw8i","name":"irc"}]},"duration":24949.38260000199,"ttft":5229.764100000262,"contextSnapshot":{"promptTokens":203135,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_oQta4ZNPTcyFgaVJ7ujenw8i|fc_099a7567dadef391016a59749d4968819b8caf1162d716b7f5","toolName":"irc","startedAt":"2026-07-17T00:17:39.430Z","intent":"Sharing release contract findings"},"id":"f8a5ce29","parentId":"8bb6d61d","timestamp":"2026-07-17T00:17:39.430Z"}
{"type":"message","id":"a7c56441","parentId":"f8a5ce29","timestamp":"2026-07-17T00:17:39.431Z","message":{"role":"toolResult","toolCallId":"call_oQta4ZNPTcyFgaVJ7ujenw8i|fc_099a7567dadef391016a59749d4968819b8caf1162d716b7f5","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- Main: injected"}],"details":{"op":"send","from":"V034ReleaseRca","to":"Main","receipts":[{"to":"Main","outcome":"injected"}]},"isError":false,"timestamp":1784247459431}}
{"type":"message","id":"d0b8a9df","parentId":"a7c56441","timestamp":"2026-07-17T00:18:07.782Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Investigating release tag discrepancies and latency causes**\n\n**Designing e2e daemon refresh test with PTYs**"},{"type":"thinking","thinking":"**Instrumenting clocks to distinguish ConPTY and transport**\n\n**Planning broker restart for v34 compatibility**\n\n**Inspecting release metadata for broker updates**"},{"type":"toolCall","id":"call_R7ish7YBHKF9DiT12TpOXSz7|fc_099a7567dadef391016a5974bfba74819b970f1be1999a61bf","name":"grep","arguments":{"i":"Tracing broker update classification","pattern":"BrokerRequired|broker.*required|broker_stale|broker image|UpdateClass|classify\\(|brain-only|BrainOnly","path":"crates/spt-daemon/src; crates/spt/src; traceable-reqs.toml; CONTEXT.md; docs","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_SPG5WuTPCeBwWG7B0tne31ix|fc_099a7567dadef391016a5974bfba80819ba55b4ce1dc86539c","name":"read","arguments":{"i":"Reading release classification","path":"crates/spt-daemon/src/update.rs","selector":"1-310"}},{"type":"toolCall","id":"call_cIDhnUurhTZjqZKDR8AXKe7d|fc_099a7567dadef391016a5974bfba88819baca1fb4397ea5f24","name":"read","arguments":{"i":"Reading image status logic","path":"crates/spt/src/cli.rs","selector":"4180-4245"}},{"type":"toolCall","id":"call_HgCLVY0DO7QBzbXuHax2wm3W|fc_099a7567dadef391016a5974bfba90819b935b27d2d09747d0","name":"read","arguments":{"i":"Reading redispatch hazard","path":"docs/KNOWN-HAZARDS.md","selector":"735-770"}},{"type":"toolCall","id":"call_993FKAayiPTuticZn9gEIYJo|fc_099a7567dadef391016a5974bfba98819b83dcd7ebc703e1a4","name":"grep","arguments":{"i":"Finding v34 release validation","pattern":"counter 61|v0\\.34\\.0|REDISPATCH-TRUTH W1|W1 gate|GATE.*redispatch|redispatch.*PASS","path":"docs; .git/logs; CHANGELOG.md","selector":null,"case":false,"gitignore":false,"skip":null}},{"type":"toolCall","id":"call_nuvbq6r1K7VzO8dZxs5zpiJ0|fc_099a7567dadef391016a5974bfba9c819bb42d54627acc69c5","name":"irc","arguments":{"i":"Checking regression archaeology","op":"inbox","to":null,"message":null,"replyTo":null,"await":null,"from":null,"timeoutMs":null,"peek":false}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":1774,"output":1295,"cacheRead":202240,"cacheWrite":0,"totalTokens":205309,"reasoningTokens":944,"cost":{"input":0.008870000000000001,"output":0.03885,"cacheRead":0.10112,"cacheWrite":0,"total":0.14884}},"stopReason":"toolUse","timestamp":1784247459438,"responseId":"resp_099a7567dadef391016a5974a3a144819ba46218c371e2db14","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a5974a502ac819bb8bbd6e6d290374e","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXSwncrfoOUQdLxUR-GTBKxaevv_ohhaqogja4YMg6wSYNSoXpf9JMPK2mAt4amPKDCZ83lnevIdIealxiGAh5pSdTw236mdZcgesDTHKg_kCLOMX68yy4mLKdEZkvdFP9B0mfEFunuRyUNWtbqEuaQQcqrWW13tCE-yjSVeiKpJGKG3GIq1SsCEaOTAtEPRsm6R4Wnv3yePxgdqepPHydp9Bl0iDx-iFcEdJQkFDoV1i2w3RLEj1NIQ6wHjCGnGUnD6Qzy121Uz-z0yV-I9gvaLQLYZFdF8biUTRiAaJGI9IbaachbVUt99BKjYEHZDuYzraqb5dacPWkleTAGeqW7ODFAKLWgvieO6O--ZAxmG4Dz2KAX5YG-Yh9kN2MTmmwBsEkvJlt8MDa1ZX0eF17VYM8dliVCESJy2Lbjzhxg4E6qlOwPMlCFdYfMfhSQOmJ7w8PE8-p34nr7xdJa708tZfCt5uPUPykBsbfswpYE9iD9Eltv0wXeNYot3Cs0IMfOOGxM0GQDQmU5mK0lk-CoAleY8q3LAFHCm-Ksc5KNE8r5d_U-DYpyBy7dLVC0XpdNLMpUiYU9xxT-c-3QJJzPECsCFm1TnXjzWdbxNiXuggORCZS8a0cUFJNoCtbiHah9_IzL4_OLuMcfnd7HSRGEDt8-wwVxyNsdqGzr52XbM9IU_ypXp5jSYlfr8xVPnFFPMr1CjW5zbQlCbjSbvwxhAwbPntD3R5EJZTyMN_dwNLiHBnyY5-Bwscl-UMTXlP85EQntYuFtKoWTM0qf_wHc2J1iL5smBJY1e7ks2Dwhd0vOt3TYcYZvxBd_tK8i5i82-Eb-ZzL77OJQQyJ-IrCPP-LZWWJIY2frc-sR7DoH0zYeZbaGkH7HDP0_UcyLjeTu-boQRpuO1rqwIgBcLSX-2n8BGmMsLj8IvnEvkkvj04CLZb5WhF9PaF2QapWgqQ3arG00wQHGizfQC6xQTrbHt6sL5pjPchdQ-86px6M9qQSqNmEzHu6xvSFdI0m-hrAsb5Z3jId2jgJQjubm19usgAYF7-M3q9-gU2x7UaRFTPf0TD9QsHQyTY5czXlDkEgG4rYME1CupNdMRLvZAvr048_g0-dNjCE6_yua6nHXty6OU-HI2506pYvb7u7WQqDXcIJnofAhARvxRiJwQFzfHo3vhYw8KHrTWcGzmYx0ExaLFm7sIXfO28OLtEOLN3ztcH0EBV6waJpiIC0xgoEPE7GXGwN3s--KEV-U5bpF9-_dyAhfkM3CZMpedHtQqioVkl9Lryzl13ErJDWOh1wECyHHtJ_cQ1j8InrJpiUDFPY_PHO1w9aXbfEf93WcHWswGjXWIa7GjDoUggwoFC7y4AgRZ6vnFUHerxjj4QMCF6IGeaL9-28RtgbOj5hTrjM1diUMO1dw4WlmHiGRf9khc7mOj2YVwpLvL2ZPttNjYjJct7xP7XOY-YUeGL6ikwrNKODWW356_9LUWIQRe9gaoLfFbwji8nk0HIfttsCeZ4a8lzuuxi26_l2X9AA_DBIPFJqEuE1-DHBVFze7Y3rsEZpJguycShFQj8zPBN5Q40PYKNdKtqrNejFLVg9NmOc0wavTia-myYjZ7OlMEHQ-O_GG5uIMmhZs8WJUB2wxHWpOQfpHyrukmZm9U5DEuqoyosw7ZUbC_vOS_02hyQdJV5dT9O6eIL5yBScq3Jkyl2BD7dbg13NgZaIA8_f5RH9EG4a4LIGHzhHXOl8CvUQK_kEqecv5BVRm9Rm4x7Hs5jzZp7syQ0j5UQAbACGUg9DuR7MzR2tQK5Gz2EwUJapFEVkCZkltj1ar8zuGsXfNyzbDZgtVaUYdnr1aUttG0cFo566LjGU5jVSdsGE4DojPQl0gHsReMrpgMhqwoqY4Gg59I8tVsRIruhr7PJeS7jUIyavLJ542hGYjDQMGqGhMxolef7mz5G4373QUMxNqwvnzNrgp_qZZA2Exi15vs05TWqYQsYdM6AemwqqVWVw83IKtEIYOMR6mz3uSSDpaDuXcIJact_UkBomaIKKDaS-8TF4US4t-hA0bUmvtHWS7Cqg7Vs2xwAR8k1V9s6b1ZTam2RWgMhQaDzC5HPRYIby4ptBpunhBDuZSh2COB6wzlkSFrOhCvGRPB26qRN_ofxGBNSCadWmUsRnZRr3wYntFI17K2sYm7MinjWM2AM1Ro-n5fztyqfw0QV2XVBrhcbD7mfBX6eVv4T-ShnACw3pebroTDjFhODkRVJeE58g4_FURWIQzjJXVQVDwcE7px1VjJsNkLGspuXtMV4j77Oquor4QjJhI_dmLLettDiNXYbbgQIDUXCWw8r1qBMbQZpvN7MkSiYJN-ZHpdnNIrPaMq49OxZS0fqvyDb5cfKTj7i0w5XeHHKcRa4ELBLMpm7p6T21GoMxz2iZgVIAa8sTS-LIoHVMktMFCJAr_5Cm23Ox8su4QHKxlwwA_58kjUepzK_ZbXZgagBve9rLpSaLQF6tewBIsrkX7B7eEEdoV-uY1-9Zyjw5jKQdIS_F6h5GRO1xszRR2Nvv6CKxF-JEW_twF9UT-8PDWajbLr_0FrJf-oUXiQPgONYmVYOcADvQ7zk2DblIz4mhIHBhzURE3uv2z_Sb8OvFVgeBxD-PvraxmQNsGYu5PNyYohijLqZuVyQdhCIA00aa-cwE-uVjgpRRPMOZBuZ0qUbt8dWqP_BRsGztf2Rtu8PBjye5bKLV-fonGlWbw1fASiNAeqK6GZ3ziLHHhV-XckcYFsYb3EdWLpLzfZXQrUy7VLhd9fWH2t_oX6wIRJXVfTAIYIb_KVq-FGPaMqM_QlZ-jSbYhup-iwHVrpxIpi6PDoyF2Kogf0C_Yo9o8vHZCapcKZRuyh1cQlhaeyEDjplOHTRSAs1ZIAffdyYLdmrI_e1QdoUtyLmtobw8CFK2SCt-TEAgXTan-u5s9gPlVmCZNzUGdkZVYOC2zgfInYTnVwip_YEIpdZpBQxUFlH59lVEaiziFobFT0Ud3zKBCrJYeAWYGQmf1iN_1LEE34eW3Tewh71ncjNGjwVX9Dn2hfpR6r0MEbU0XUb73-igEMjr_RbBW8qZ25v2IrjmQczLnTF4BvMdFfjHb0GYKcIeKnjRfhbbk25NlZPJkpFO6sGzGvRLpdBD0juEMOG8ayIUjhxB3G6_KMCB9tM5WfhZc8uBvoDmP9ZjVkQxdhIpka9Ga55ous6yM_FZAJS-shR5J35FJvXo4x1kHqYFXjcxBeyFn8bg6EtMOrqLeW1-Dcd-a8y6-kQZr-cVCDbK1xOpD4eA8QPWY-GECQhYzcSzvdf2Zy4l33wEedCxRZTuBv6-G7yOc-L4CbX9rty2sFRBNXblOscGIMblpwSkFG7oTyJt5mNJ6ri7Fj3_YQzQu7CQIB0HuCTU0dgRqspO5FVXK7XmjXtDc3rRmaGR_UERu8fN6HiRpFimeJwHbNL7ELnAk6bv2Wv9coAVKDuFCHByrIyA_wBPkd28VhubUA2PSn7suHHVmtHudSmx-E1Y_dSJntEEK5B1LRnWaT_s-P9aKgJl_xD7qkiU3QeR8ghP0X864hIBeaP-JH38U8dnYZ3-e6C7OB8nHkWeJoAo5Kz_pJD_qEpTuN8IQgkYeYJtFpYvGsr0eKFBcwOt-AbgZMNwjAvZ2NefPO2rTqWNGhYRGUVot5RBn8jukmsM14wbuAKPYfAT_N2QEw6QWqMmwpSmA13-zmTAnQUpQtbo2AcM91F4N2odctaGizYPBxpevx6yasCekMbNIBO8u03oMS7zulyAjUidEH6OWJDsosEMkCvd5fmK-tJHz_PCAHe4441BiYBlFph_oZ4uOQPH9bhsoph5Rvp2p9i7rdgBtuaeXUXIQ27YoosjYYNIJGfONM7L5K2NhH-qGENXv-aWjOcT7ItTXqP4aJwQjamms4M1hQFC7Bj0k8ZDItCtPFHd4sNTdpky2nJobWHLEz6uGQqe_1IAdTtgBZnGQrN63L2zX6dMIi7TB35ThRJQMG8p0iYr7bOOhP_D2-hFeNac7d5-TDaiwJzw==","summary":[{"type":"summary_text","text":"**Investigating release tag discrepancies and latency causes**"},{"type":"summary_text","text":"**Designing e2e daemon refresh test with PTYs**"}]},{"id":"rs_099a7567dadef391016a5974b07154819bb1c06832efc0de65","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXS_TJl3I4g_0qRIsJMQRm3zO2b5XWnFYFfyIFMn4R8iHXFkBQGw9Pc0NJEqr-BU4q4RiqL9Jj7JaylhqU8vWxZFXxf1Bgqsd-l4m0um3yKx0sKKywoH_ue-lPsctWkgBzLr3rInqUCN-Qd3eE2e1ak5r8rdLsFl_3P3tXJO4Z4U6MwPhEpxuKSgO91eNflV8dmJ3KGHwHawhzgnuk0zQZZCrfS6rywhRZFv-aVoJPX_k5qgfCeWztsHWZMt-0KVgcE6BWl9JsrNYUvrv-cq4bDgXzYwx5oVYctaTjtG110_3iCL14AldmyvCYM_hLAi6ryRbNc9OuzaH3PBBmw4B9gjSu6NnH1UI_OdirhQoguy9qjT_RkEEgGSAAS1_-jHK-XfOLYlyZzu09HgnnIlvktZOSUsOgIl6xUD4Zz80hW_TMY2w6-2Byi_bjakkDoZqVOUpJMbXtNdOxBJl2ADNEes8Rn2a2JN_t74sZxlvf4_i1wFiLqIwAN5ZsP49ZJ6qPyAqJ7dkY27FgdQZcYIELSdgPnc6JeD8BPSFYFtht-_R1OVRG9nBC5IrH23FEv6DVWqlnCAQbAb6Xft8dmMh8kvCjfoIUEaPqd6FSZ0JP6xbzrEagk03H3fY_rGBQ0dY84Wm7zuj2_ZFFGFfSmXvPkqFCmeB5n8bBKC0pM0yQvFT5mpr7j4G77NIAgkLRd66Y1gx3QvP1Gyt5vlQHjSnyZhCMogrTtUuo1aam1A0ElW2w-QTpelLX6NPvb-qPPiJZqWgcK3T06mX2mHAt44I1VGHtsplAzOHRnJ96WAOyYYksiDdvdpSZYX155_ysPFPD5MT1RIpNUpyppVk9_SMrM4KFkddbrifvBeQhkeSHHaWgNTdBcgZr7skBrp9hI3r_YMXywSacQ7FoKmdeFfrMcrugbZatRYQG7jx4ujrkdFeNrOyFpOi36wFmK2pavLS6iF54QO-a7TQLWikogPjbDUkgEwLzU9DEpDvAiWDhuI2KjIFmdxAfmV7bWEf88V-YUGX8tvou3L8AWwp4YueWGNMQHUOoE_UXMwnCBbbb30H3COv48ug6d4twPFasxmru60qQPiHNZb5Wn5rdM20TLHs3rO9CtGd9ovkByqwvU5SMeOq-QmBuzAJITKIwI4Au6sCtFvMwJYbbKtc3UEzfZKZz_ZQkdzVXNP0uaBip3Xihz8uJHe_g6XDJP4Tp40kQ5UWjoOKos9vCTU_Ycb_pyFpDAR5f4QHCKoHVAvEiiRIkiwvgsrlUcOT7pu0nYr0rQi4jGI7xDzqUeZp1CtXQtw2UO8p2qG1Cw7clQy5cHRrEUi5lE2XXo0PF5aOS7DaYjzQ5FnRmQcSQLNsRIjtnCI5fCdLij7UKjE3xZ1mK_2u0_2H8CTCvFY3G4XXqNzgoK0cmAKlke1pMRPFlynHBLOkP6-YwWKBphA6b37rd_23m_XZKVbJIk3l4qL8os0g68N32gWu9ZPYz7gKTJXgCLpaVCiCUZNy1sWOX5AvgTjtoWK4lFZspG1HfpqNBm9p5m5186Z1vPEmRXX-zSf7aNBNhjihrv7gh0B06k2yeSktL-4TzXAOCe3J52KTXBIpJfLgzpgcEpaynVONkBegb7WhK3rkHbGJ5JATobt3iNL8mv1yJbLJPUztpRATXEAGbjA3iJgnJXvM2eV5FM7bQDlKgyQIg773tI2xh8MM7xLIoxm5ltgwcQY2PBYHofBs1RjTEdgJVLjrgJT0eqP5a-wUAp5PpWu9wHLPfqoe_Ic7rjqY70p68RvqJ9W1pV1NVirWZUqx_3_-Yvr_XGQe05bSMTOkGug4KOX6Rsd3r1_Px6Lns5SGlhFSt-g_07-e0nGhjciXl5FpV9alfo5vfOT81qSYAi9W8Hse6MNJKtaotUi2JgXFgQkn5759m6g20w24UWSgKRSr3IlLJvW9C62dLhkG8nQ_QazAHnpHKUjbIo0Cf7xjEbszPlkox6NCdoCTcYoCJH0O_r6hb7OUSkXAYaT7-90-1Vl160vmO7m0YjoBo6srhJ8FTMjH7DeqlK6kh4vrsJG4_3yBG1FHgSunFweYqpdI3A4e9TTHPFfzYfudhi83I8JR5N5QbTWMDkDo08WrqxRFBqYqnkRCKjwUwLhmYk5pUvhl98l-fuvePY6ve4u42rT4nu5dtsy4lr_ijQva6Fyl2f7BEGzQIcaRxeoBlKuKauJSiM2-CaNKl6nZ8GSvA9Tg3zRctcaRT08D7ZDaY0aV22zXV8Mpi7Rb86_WRT2fNvvshXeANc2GEHOawxoGjgtvUEmoh3Ih8N9arKuYB-jlBfKpdXATQ6olOBhB1I3xCy1-m8BiN5qVttRAnfB8G2tEduPCZ4jm18c83_rVI2DkJ9BxycpO_UzeilJQPJMW60fsIqLeO0SRBO0qbWZF6L1I4QkpmBlIstDJyWZukrji93O4XruRPZg8DHY7myPfEjEHNUdUYJNbDjq38NdHgEURaZf6MtTyBigi8iI8mGsMDtAqyptpZVPHKI89nEIXjzDT7tUBZsu24F7L-tvjVqRdnf4BG5QcaYKi62a3MSoxYOXkw32dcNXTndlXNB6iox01Dbq07qThmwLFYZYXuTdM0RPwx0TIKYHfku5orjhGhIytwAGb31TqVnFC2jgeHDWHFSZIC1lJMoKFU-3QvsLQ6sbbcz7Cb5dlrqo-M4K81RmwurAE1RhSbi8tibN7fnEtscPI0PK1gyUMrvKEHkf-5f9BksRI0vfZZzkqU6e7i4PE2BfQajECxIBIPPK7YOhlqtXSQJfxK7BCZW412sexSuNGd6gupDPTAnVYD15kZYoiPFR1CK13sMsuxNP3SvWV6QULtxJQg2UvCJZyGJfq059KWLegzwNnHSMO6fKA8MTF61ir7myN1Qnmf0MxxcVbbpngRMQFsFplNyPHG9kjIh08dQb5BBQjkIcxx34lNMoKYZLvLwTcnBMgHi6jWCdoF7W_hgQuN3zOL34t78v4-kFPg0m2pAZ91SYlyiII5euzZn3F5dN3prqXLjS2Sm5s_-l5xSHV8evth4lmnwXUfJVAvhHOKZS_gioMM_TorgomHMw6MVFQduA4tw5vY5S61elVLEYIjidogXdELDiD96eCAOwB_afmZmwqQraLo_MhlnB1bvb4Iv9c6x45g5lYauwdyMp4Zd8VT9Uzj8a2WrJMTTO9o3Uk2Lal3QX2bZBkUwDiZDlBOQajV4VlyqF7gdPoRr0pMeicLb-OQ7-YshQzQx129GBsSWLrsAur4D_MgnnzpvPuqS6F4KuTHxAe483jV8xai0FrvQaPiJkZzuiz_uL0Ucixq-3olCLJpURDn_HSQtjgE3U44_3PcDQyNDD85EdzfCmVobtSM0mv1QY0ZG2XdCSYucvh1IovrTK6JPNWwXWzs7GjTWK-rtbqeWM6MstoinuwVP_Tup8O4E-Y93p9J6sFlUdiZmWonRs7EC2VA4ivGIo_9vruDRaP-ld4pLFQsdZDxf3v77gGuVSfZSfzzd28dJI3Bg6ZDDrM1A5lP9OH5JyHuqllaeyHWGoON_O2YyyXJpM7EhucsJKEVu3ZyYfyx3fEkqhpvdesS6iezSaAj2_XDavSin9KPjjWUv-TBIjiueGDsT2jDW3rjMwZKUeH1gGZCArgdaY6Z5NKA1r_exFxmGo92htNZ9HopHwldWXKTTigden5mpw9TcrDkL22QcQzUS-DL6qTV3xMn16k6uGfckOijkiMk_vyZQcf1wohbwjaLOnAhv0WPYNtRmOJFXdWi-gHbPDd0Pm56CvXnpCZbvQhBjXmspgzxAt5bg=","summary":[{"type":"summary_text","text":"**Instrumenting clocks to distinguish ConPTY and transport**"},{"type":"summary_text","text":"**Planning broker restart for v34 compatibility**"},{"type":"summary_text","text":"**Inspecting release metadata for broker updates**"}]},{"id":"fc_099a7567dadef391016a5974bfba74819b970f1be1999a61bf","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing broker update classification\",\"pattern\":\"BrokerRequired|broker.*required|broker_stale|broker image|UpdateClass|classify\\\\(|brain-only|BrainOnly\",\"path\":\"crates/spt-daemon/src; crates/spt/src; traceable-reqs.toml; CONTEXT.md; docs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_R7ish7YBHKF9DiT12TpOXSz7","name":"grep"},{"id":"fc_099a7567dadef391016a5974bfba80819ba55b4ce1dc86539c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading release classification\",\"path\":\"crates/spt-daemon/src/update.rs\",\"selector\":\"1-310\"}","call_id":"call_SPG5WuTPCeBwWG7B0tne31ix","name":"read"},{"id":"fc_099a7567dadef391016a5974bfba88819baca1fb4397ea5f24","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading image status logic\",\"path\":\"crates/spt/src/cli.rs\",\"selector\":\"4180-4245\"}","call_id":"call_cIDhnUurhTZjqZKDR8AXKe7d","name":"read"},{"id":"fc_099a7567dadef391016a5974bfba90819b935b27d2d09747d0","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading redispatch hazard\",\"path\":\"docs/KNOWN-HAZARDS.md\",\"selector\":\"735-770\"}","call_id":"call_HgCLVY0DO7QBzbXuHax2wm3W","name":"read"},{"id":"fc_099a7567dadef391016a5974bfba98819b83dcd7ebc703e1a4","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding v34 release validation\",\"pattern\":\"counter 61|v0\\\\.34\\\\.0|REDISPATCH-TRUTH W1|W1 gate|GATE.*redispatch|redispatch.*PASS\",\"path\":\"docs; .git/logs; CHANGELOG.md\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_993FKAayiPTuticZn9gEIYJo","name":"grep"},{"id":"fc_099a7567dadef391016a5974bfba9c819bb42d54627acc69c5","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking regression archaeology\",\"op\":\"inbox\",\"to\":null,\"message\":null,\"replyTo\":null,\"await\":null,\"from\":null,\"timeoutMs\":null,\"peek\":false}","call_id":"call_nuvbq6r1K7VzO8dZxs5zpiJ0","name":"irc"}]},"duration":28342.57460000366,"ttft":1568.6627999991179,"contextSnapshot":{"promptTokens":204014,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_R7ish7YBHKF9DiT12TpOXSz7|fc_099a7567dadef391016a5974bfba74819b970f1be1999a61bf","toolName":"grep","startedAt":"2026-07-17T00:18:07.782Z","args":{"path":"crates/spt-daemon/src; crates/spt/src; traceable-reqs.toml; CONTEXT.md; docs"},"intent":"Tracing broker update classification"},"id":"49fe1375","parentId":"d0b8a9df","timestamp":"2026-07-17T00:18:07.782Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_SPG5WuTPCeBwWG7B0tne31ix|fc_099a7567dadef391016a5974bfba80819ba55b4ce1dc86539c","toolName":"read","startedAt":"2026-07-17T00:18:07.782Z","args":{"path":"crates/spt-daemon/src/update.rs"},"intent":"Reading release classification"},"id":"4c685c9d","parentId":"49fe1375","timestamp":"2026-07-17T00:18:07.782Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_cIDhnUurhTZjqZKDR8AXKe7d|fc_099a7567dadef391016a5974bfba88819baca1fb4397ea5f24","toolName":"read","startedAt":"2026-07-17T00:18:07.782Z","args":{"path":"crates/spt/src/cli.rs"},"intent":"Reading image status logic"},"id":"4e06afd6","parentId":"4c685c9d","timestamp":"2026-07-17T00:18:07.782Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_HgCLVY0DO7QBzbXuHax2wm3W|fc_099a7567dadef391016a5974bfba90819b935b27d2d09747d0","toolName":"read","startedAt":"2026-07-17T00:18:07.782Z","args":{"path":"docs/KNOWN-HAZARDS.md"},"intent":"Reading redispatch hazard"},"id":"4f9182c0","parentId":"4e06afd6","timestamp":"2026-07-17T00:18:07.782Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_993FKAayiPTuticZn9gEIYJo|fc_099a7567dadef391016a5974bfba98819b83dcd7ebc703e1a4","toolName":"grep","startedAt":"2026-07-17T00:18:07.782Z","args":{"path":"docs; .git/logs; CHANGELOG.md"},"intent":"Finding v34 release validation"},"id":"ffd2a531","parentId":"4f9182c0","timestamp":"2026-07-17T00:18:07.782Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_nuvbq6r1K7VzO8dZxs5zpiJ0|fc_099a7567dadef391016a5974bfba9c819bb42d54627acc69c5","toolName":"irc","startedAt":"2026-07-17T00:18:07.782Z","intent":"Checking regression archaeology"},"id":"b3648861","parentId":"ffd2a531","timestamp":"2026-07-17T00:18:07.782Z"}
{"type":"message","id":"11f467bd","parentId":"b3648861","timestamp":"2026-07-17T00:18:07.782Z","message":{"role":"toolResult","toolCallId":"call_nuvbq6r1K7VzO8dZxs5zpiJ0|fc_099a7567dadef391016a5974bfba9c819bb42d54627acc69c5","toolName":"irc","content":[{"type":"text","text":"Inbox empty."}],"details":{"op":"inbox","from":"V034ReleaseRca","inbox":[]},"isError":false,"useless":true,"timestamp":1784247487782}}
{"type":"message","id":"09634095","parentId":"11f467bd","timestamp":"2026-07-17T00:18:07.787Z","message":{"role":"toolResult","toolCallId":"call_SPG5WuTPCeBwWG7B0tne31ix|fc_099a7567dadef391016a5974bfba80819ba55b4ce1dc86539c","toolName":"read","content":[{"type":"text","text":"//! The self-update engine (C0) — the update-class taxonomy and the brain-only\n//! zero-interruption swap.\n//!\n//! ADR-0004 §A splits a self-update into **three classes**, each with its own\n//! invariant, decided on two axes against the *running* broker:\n//!\n//! - **brain-only** — the routine case. The running broker can already host the\n//!   release's brain (the versioned-IPC handshake accepts it) and the broker's\n//!   held-resource ABI is unchanged, so only the logic swaps. **No endpoint\n//!   process terminates or suspends** ([`REQ-UPD-3`], absolute for this class) —\n//!   proven E2E over the M3b handoff substrate.\n//! - **broker-compatible** — the release ships a newer broker that the running\n//!   broker's IPC floor cannot host the new brain against, but the held-resource\n//!   *type* is unchanged, so the new broker hot-adopts the existing handles\n//!   behind the versioned IPC; endpoints still survive. (Apply path deferred —\n//!   no spike yet, ADR-0004 §A.)\n//! - **broker-breaking** — the held-resource *type* or an OS PTY/socket API\n//!   changed; this needs a *planned endpoint-cycle* and MAY suspend endpoints\n//!   with consent + scheduling. [`REQ-UPD-3`] is explicitly weakened here.\n//!\n//! C0 implements the **classifier** ([`classify`]) and the **brain-only apply**\n//! ([`apply_brain_only`]). The apply *triggers a handoff* over the existing M3b\n//! substrate ([`crate::brain::Brain::handoff`]) — it does **not** re-implement\n//! one (clean-room posture, `M3c-PLAN.md` §C0). The signing/rollback hardening\n//! that gates *which* binary is even eligible to swap arrives in C1.\n// [impl->REQ-UPD-3]\n\nuse std::io;\n\nuse crate::brain::{Brain, BrainState};\nuse crate::frame::{IPC_PROTOCOL_VERSION, MIN_COMPATIBLE_VERSION};\nuse crate::release::{\n    verify_artifact, verify_metadata, verify_update_set_artifact, verify_update_set_metadata,\n    RejectReason, SignedRelease, SignedUpdateSet, VerifyPolicy,\n};\n\n/// The held-resource-type ABI of this build's broker — the broker-breaking axis\n/// (ADR-0004 §A/§B). Bumped **only** when the *kind* of resource the broker holds\n/// changes: a new held-resource type, or a new OS PTY/socket API that changes how\n/// an existing handle is carried. A pure logic change (the routine update) never\n/// touches this — that is precisely what keeps the routine case *brain-only*.\npub const BROKER_RESOURCE_ABI: u32 = 1;\n\n/// What the currently-running broker provides — the fixed point an incoming\n/// release is classified against (ADR-0004 §A). Captured from the live broker,\n/// not from this build, because the whole point of a self-update is that the\n/// *running* broker may be older than the binary proposing the update.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct BrokerAbi {\n    /// The IPC protocol version the running broker speaks.\n    pub ipc_version: u32,\n    /// The oldest brain IPC version the running broker will handshake\n    /// (`accept_hello`'s floor). A release brain at or above this can attach to\n    /// the running broker as-is.\n    pub min_compatible: u32,\n    /// The held-resource-type ABI tag ([`BROKER_RESOURCE_ABI`]) — the\n    /// broker-breaking axis.\n    pub resource_abi: u32,\n}\n\nimpl BrokerAbi {\n    /// The ABI of *this* build's broker. In the live daemon the running broker's\n    /// ABI is read off the broker over the IPC; this is the value a broker built\n    /// from this source reports (and the classifier's stand-in when the running\n    /// broker is this same build).\n    pub fn current() -> Self {\n        BrokerAbi {\n            ipc_version: IPC_PROTOCOL_VERSION,\n            min_compatible: MIN_COMPATIBLE_VERSION,\n            resource_abi: BROKER_RESOURCE_ABI,\n        }\n    }\n}\n\n/// What a candidate release declares about its broker/brain — the subset the\n/// classifier needs (C1 adds version + signature + expiry). Forward-compatible\n/// by intent: only the two ADR-0004 §A axes live here.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct ReleaseSpec {\n    /// The IPC protocol version the release's **brain** speaks.\n    pub brain_ipc_version: u32,\n    /// The held-resource-type ABI the release's **broker** implements — compared\n    /// against the running broker's [`BrokerAbi::resource_abi`] to detect a\n    /// broker-breaking change.\n    pub broker_resource_abi: u32,\n}\n\n/// The three update classes (ADR-0004 §A), each routed to its own invariant.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum UpdateClass {\n    /// Swap logic only; the running broker hosts the new brain unchanged. No\n    /// endpoint terminates ([`REQ-UPD-3`], absolute). The routine case.\n    BrainOnly,\n    /// The broker binary hot-swaps behind the versioned IPC (held-resource type\n    /// unchanged); endpoints survive. Apply path deferred (no spike — ADR-0004 §A).\n    BrokerCompatible,\n    /// Held-resource-type / OS-API change; a planned endpoint-cycle is required\n    /// and endpoints MAY suspend with consent + scheduling. [`REQ-UPD-3`] weakened.\n    BrokerBreaking,\n}\n\n/// Classify a candidate release against the running broker (ADR-0004 §A).\n///\n/// The decision is purely the two ADR axes:\n/// 1. **held-resource ABI** — if the release's broker implements a *different*\n///    `resource_abi`, the held-resource type changed: **broker-breaking**,\n///    regardless of IPC (a planned endpoint-cycle is unavoidable).\n/// 2. **IPC hostability** — with the resource type unchanged, ask whether the\n///    *running* broker can host the release's brain. If the brain's IPC version\n///    is at or above the running broker's floor, the handshake accepts it and\n///    only logic swaps: **brain-only**. Otherwise the broker must move to a build\n///    that speaks the new IPC — but since the resource type is unchanged the new\n///    broker adopts the existing handles: **broker-compatible**.\n// [impl->REQ-UPD-3]\npub fn classify(running: &BrokerAbi, release: &ReleaseSpec) -> UpdateClass {\n    if release.broker_resource_abi != running.resource_abi {\n        return UpdateClass::BrokerBreaking;\n    }\n    if release.brain_ipc_version >= running.min_compatible {\n        UpdateClass::BrainOnly\n    } else {\n        UpdateClass::BrokerCompatible\n    }\n}\n\n/// A classified update: the class plus the release that produced it. Built by\n/// [`plan_update`] so the class is always derived from the same `(running,\n/// release)` pair the apply step will act on.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct UpdatePlan {\n    /// The update class (ADR-0004 §A).\n    pub class: UpdateClass,\n    /// The release the plan was built for.\n    pub release: ReleaseSpec,\n}\n\n/// Classify `release` against `running` and bundle the verdict with it.\npub fn plan_update(running: &BrokerAbi, release: ReleaseSpec) -> UpdatePlan {\n    UpdatePlan {\n        class: classify(running, &release),\n        release,\n    }\n}\n\n/// The verify-before-handoff gate (C1): authenticate a signed release + confirm\n/// the binary bytes, then — and only then — classify it into an [`UpdatePlan`].\n///\n/// This is the **front door** of an update: ADR-0004 §D makes signing mandatory,\n/// so a release is never even classified (let alone applied) until it has passed\n/// [`verify_metadata`] (signature + key trust/revocation + channel + expiry +\n/// monotonic version) **and** [`verify_artifact`] (the real bytes match the\n/// signed digest). A rejected release returns its [`RejectReason`] and **no plan\n/// is produced** — there is nothing for [`apply_brain_only`] to act on, so an\n/// unverified binary can never reach a handoff ([`REQ-UPD-2`]).\n// [impl->REQ-UPD-2]\n// [impl->REQ-HAZARD-UPDATE-ROLLBACK]\npub fn plan_verified(\n    running: &BrokerAbi,\n    signed: &SignedRelease,\n    artifact: &[u8],\n    policy: &VerifyPolicy,\n) -> Result<UpdatePlan, RejectReason> {\n    let meta = verify_metadata(signed, policy)?;\n    verify_artifact(&meta, artifact)?;\n    Ok(plan_update(running, meta.spec()))\n}\n\n/// The update-set front door: authenticate a signed multi-platform set, select\n/// the recipient's Rust target triple, confirm those bytes match the signed\n/// digest, then classify that platform artifact.\n// [impl->REQ-UPD-6]\npub fn plan_verified_update_set(\n    running: &BrokerAbi,\n    signed: &SignedUpdateSet,\n    platform: &str,\n    artifact: &[u8],\n    policy: &VerifyPolicy,\n) -> Result<UpdatePlan, RejectReason> {\n    let meta = verify_update_set_metadata(signed, policy)?;\n    let selected = meta.artifact_for(platform)?;\n    verify_update_set_artifact(&meta, platform, artifact)?;\n    Ok(plan_update(running, selected.spec()))\n}\n\n/// Why a brain-only apply did not complete.\n#[derive(Debug)]\npub enum ApplyError {\n    /// The plan is not brain-only — routing it through the brain-only path would\n    /// risk an endpoint, which this class must never do. Raised **before** the\n    /// outgoing brain is touched. The caller routes a broker-compatible /\n    /// broker-breaking plan through its own (consent-gated, scheduled) path;\n    /// brain-only is the only swap C0 performs.\n    NotBrainOnly(UpdateClass),\n    /// The new brain failed to re-attach (the `relaunch` closure errored). The\n    /// endpoint is **untouched** regardless — the broker + its hosted child\n    /// persist across a failed re-attach exactly as across a brain crash — so the\n    /// caller may retry against the still-live broker.\n    Relaunch(io::Error),\n}\n\nimpl std::fmt::Display for ApplyError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            ApplyError::NotBrainOnly(c) => {\n                write!(\n                    f,\n                    \"apply_brain_only refused a {c:?} plan (would risk an endpoint)\"\n                )\n            }\n            ApplyError::Relaunch(e) => {\n                write!(f, \"brain-only relaunch failed (endpoint intact): {e}\")\n            }\n        }\n    }\n}\n\nimpl std::error::Error for ApplyError {\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\n        match self {\n            ApplyError::NotBrainOnly(_) => None,\n            ApplyError::Relaunch(e) => Some(e),\n        }\n    }\n}\n\n/// Apply a **brain-only** update ([`REQ-UPD-3`]): swap the brain logic without\n/// disturbing any broker-held resource or hosted endpoint.\n///\n/// This is a deliberate trigger of the M3b handoff path, **not** a new handoff:\n/// it snapshots the outgoing brain ([`Brain::snapshot`]), drops it — the exact\n/// crash boundary the broker already tolerates (the subscriber vanishes; the\n/// child + its buffered output persist) — and re-attaches by calling `relaunch`\n/// with the snapshot. `relaunch` stands for \"exec the new binary's brain, which\n/// re-attaches via [`Brain::handoff`]\"; in the live daemon it execs the verified\n/// new binary, and the returned brain is generation+1 with a fresh `gen_start`.\n///\n/// The guard is the invariant: a non-brain-only `plan` is refused\n/// ([`ApplyError::NotBrainOnly`]) **before** the outgoing brain is dropped, so\n/// the brain-only path can never be the thing that cycles an endpoint. Broker\n/// touching classes route elsewhere (C2 consent + scheduling).\n///\n/// The production caller is [`crate::applyhost::apply_staged`] (M5-D7b): the\n/// peer pump still stops at *staged + consent-notified* (REQ-UPD-4 gated\n/// default), and the explicit user ack (`spt update apply`) drives the\n/// re-verify + class-check + brain handoff orchestration through this seam.\n// [impl->REQ-UPD-3]\npub fn apply_brain_only<F>(\n    plan: &UpdatePlan,\n    outgoing: Brain,\n    relaunch: F,\n) -> Result<Brain, ApplyError>\nwhere\n    F: FnOnce(BrainState) -> io::Result<Brain>,\n{\n    if plan.class != UpdateClass::BrainOnly {\n        // Refuse *before* touching the outgoing brain — no endpoint is at risk.\n        return Err(ApplyError::NotBrainOnly(plan.class));\n    }\n    let state = outgoing.snapshot();\n    drop(outgoing); // the crash boundary the broker tolerates: child + log persist\n                    // The new binary's brain re-attaches over the existing substrate. A relaunch\n                    // failure surfaces to the caller, which may retry against the still-live\n                    // broker (the endpoint is untouched regardless — that is the whole point).\n    relaunch(state).map_err(ApplyError::Relaunch)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    // [unit->REQ-UPD-3] same resource ABI + a brain the running broker can host\n    // (IPC at or above the floor) ⇒ brain-only: the routine, endpoint-preserving\n    // case. A *newer* brain (higher IPC) against the running broker is still\n    // brain-only — the versioned IPC accepts a newer brain on an older broker.\n    #[test]\n    fn classify_brain_only_when_broker_hosts_new_brain() {\n        let running = BrokerAbi::current();\n        let same_ipc = ReleaseSpec {\n            brain_ipc_version: running.ipc_version,\n            broker_resource_abi: running.resource_abi,\n        };\n        assert_eq!(classify(&running, &same_ipc), UpdateClass::BrainOnly);\n\n        let newer_brain = ReleaseSpec {\n            brain_ipc_version: running.ipc_version + 5,\n            broker_resource_abi: running.resource_abi,\n        };\n        assert_eq!(\n            classify(&running, &newer_brain),\n            UpdateClass::BrainOnly,\n            \"a newer brain the running broker still accepts is brain-only\"\n        );\n    }\n\n    // [unit->REQ-UPD-3] same resource ABI but the new brain is below the running\n    // broker's IPC floor ⇒ broker-compatible: the broker binary must move to host\n    // it, but the held-resource type is unchanged so endpoints still survive.\n    #[test]\n    fn classify_broker_compatible_when_brain_below_floor() {\n        // A running broker with a raised floor (it has already moved past v1).\n        let running = BrokerAbi {\n            ipc_version: 3,\n            min_compatible: 3,\n            resource_abi: BROKER_RESOURCE_ABI,\n        };\n        let old_brain = ReleaseSpec {\n            brain_ipc_version: 2,\n            broker_resource_abi: BROKER_RESOURCE_ABI,\n        };\n        assert_eq!(\n            classify(&running, &old_brain),\n            UpdateClass::BrokerCompatible\n        );\n    }\n…\n}\n\n[Showing lines 1-313 of 529. Use :314 to continue]"}],"details":{"truncation":{"content":"//! The self-update engine (C0) — the update-class taxonomy and the brain-only\r\n//! zero-interruption swap.\r\n//!\r\n//! ADR-0004 §A splits a self-update into **three classes**, each with its own\r\n//! invariant, decided on two axes against the *running* broker:\r\n//!\r\n//! - **brain-only** — the routine case. The running broker can already host the\r\n//!   release's brain (the versioned-IPC handshake accepts it) and the broker's\r\n//!   held-resource ABI is unchanged, so only the logic swaps. **No endpoint\r\n//!   process terminates or suspends** ([`REQ-UPD-3`], absolute for this class) —\r\n//!   proven E2E over the M3b handoff substrate.\r\n//! - **broker-compatible** — the release ships a newer broker that the running\r\n//!   broker's IPC floor cannot host the new brain against, but the held-resource\r\n//!   *type* is unchanged, so the new broker hot-adopts the existing handles\r\n//!   behind the versioned IPC; endpoints still survive. (Apply path deferred —\r\n//!   no spike yet, ADR-0004 §A.)\r\n//! - **broker-breaking** — the held-resource *type* or an OS PTY/socket API\r\n//!   changed; this needs a *planned endpoint-cycle* and MAY suspend endpoints\r\n//!   with consent + scheduling. [`REQ-UPD-3`] is explicitly weakened here.\r\n//!\r\n//! C0 implements the **classifier** ([`classify`]) and the **brain-only apply**\r\n//! ([`apply_brain_only`]). The apply *triggers a handoff* over the existing M3b\r\n//! substrate ([`crate::brain::Brain::handoff`]) — it does **not** re-implement\r\n//! one (clean-room posture, `M3c-PLAN.md` §C0). The signing/rollback hardening\r\n//! that gates *which* binary is even eligible to swap arrives in C1.\r\n// [impl->REQ-UPD-3]\r\n\r\nuse std::io;\r\n\r\nuse crate::brain::{Brain, BrainState};\r\nuse crate::frame::{IPC_PROTOCOL_VERSION, MIN_COMPATIBLE_VERSION};\r\nuse crate::release::{\r\n    verify_artifact, verify_metadata, verify_update_set_artifact, verify_update_set_metadata,\r\n    RejectReason, SignedRelease, SignedUpdateSet, VerifyPolicy,\r\n};\r\n\r\n/// The held-resource-type ABI of this build's broker — the broker-breaking axis\r\n/// (ADR-0004 §A/§B). Bumped **only** when the *kind* of resource the broker holds\r\n/// changes: a new held-resource type, or a new OS PTY/socket API that changes how\r\n/// an existing handle is carried. A pure logic change (the routine update) never\r\n/// touches this — that is precisely what keeps the routine case *brain-only*.\r\npub const BROKER_RESOURCE_ABI: u32 = 1;\r\n\r\n/// What the currently-running broker provides — the fixed point an incoming\r\n/// release is classified against (ADR-0004 §A). Captured from the live broker,\r\n/// not from this build, because the whole point of a self-update is that the\r\n/// *running* broker may be older than the binary proposing the update.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub struct BrokerAbi {\r\n    /// The IPC protocol version the running broker speaks.\r\n    pub ipc_version: u32,\r\n    /// The oldest brain IPC version the running broker will handshake\r\n    /// (`accept_hello`'s floor). A release brain at or above this can attach to\r\n    /// the running broker as-is.\r\n    pub min_compatible: u32,\r\n    /// The held-resource-type ABI tag ([`BROKER_RESOURCE_ABI`]) — the\r\n    /// broker-breaking axis.\r\n    pub resource_abi: u32,\r\n}\r\n\r\nimpl BrokerAbi {\r\n    /// The ABI of *this* build's broker. In the live daemon the running broker's\r\n    /// ABI is read off the broker over the IPC; this is the value a broker built\r\n    /// from this source reports (and the classifier's stand-in when the running\r\n    /// broker is this same build).\r\n    pub fn current() -> Self {\r\n        BrokerAbi {\r\n            ipc_version: IPC_PROTOCOL_VERSION,\r\n            min_compatible: MIN_COMPATIBLE_VERSION,\r\n            resource_abi: BROKER_RESOURCE_ABI,\r\n        }\r\n    }\r\n}\r\n\r\n/// What a candidate release declares about its broker/brain — the subset the\r\n/// classifier needs (C1 adds version + signature + expiry). Forward-compatible\r\n/// by intent: only the two ADR-0004 §A axes live here.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub struct ReleaseSpec {\r\n    /// The IPC protocol version the release's **brain** speaks.\r\n    pub brain_ipc_version: u32,\r\n    /// The held-resource-type ABI the release's **broker** implements — compared\r\n    /// against the running broker's [`BrokerAbi::resource_abi`] to detect a\r\n    /// broker-breaking change.\r\n    pub broker_resource_abi: u32,\r\n}\r\n\r\n/// The three update classes (ADR-0004 §A), each routed to its own invariant.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub enum UpdateClass {\r\n    /// Swap logic only; the running broker hosts the new brain unchanged. No\r\n    /// endpoint terminates ([`REQ-UPD-3`], absolute). The routine case.\r\n    BrainOnly,\r\n    /// The broker binary hot-swaps behind the versioned IPC (held-resource type\r\n    /// unchanged); endpoints survive. Apply path deferred (no spike — ADR-0004 §A).\r\n    BrokerCompatible,\r\n    /// Held-resource-type / OS-API change; a planned endpoint-cycle is required\r\n    /// and endpoints MAY suspend with consent + scheduling. [`REQ-UPD-3`] weakened.\r\n    BrokerBreaking,\r\n}\r\n\r\n/// Classify a candidate release against the running broker (ADR-0004 §A).\r\n///\r\n/// The decision is purely the two ADR axes:\r\n/// 1. **held-resource ABI** — if the release's broker implements a *different*\r\n///    `resource_abi`, the held-resource type changed: **broker-breaking**,\r\n///    regardless of IPC (a planned endpoint-cycle is unavoidable).\r\n/// 2. **IPC hostability** — with the resource type unchanged, ask whether the\r\n///    *running* broker can host the release's brain. If the brain's IPC version\r\n///    is at or above the running broker's floor, the handshake accepts it and\r\n///    only logic swaps: **brain-only**. Otherwise the broker must move to a build\r\n///    that speaks the new IPC — but since the resource type is unchanged the new\r\n///    broker adopts the existing handles: **broker-compatible**.\r\n// [impl->REQ-UPD-3]\r\npub fn classify(running: &BrokerAbi, release: &ReleaseSpec) -> UpdateClass {\r\n    if release.broker_resource_abi != running.resource_abi {\r\n        return UpdateClass::BrokerBreaking;\r\n    }\r\n    if release.brain_ipc_version >= running.min_compatible {\r\n        UpdateClass::BrainOnly\r\n    } else {\r\n        UpdateClass::BrokerCompatible\r\n    }\r\n}\r\n\r\n/// A classified update: the class plus the release that produced it. Built by\r\n/// [`plan_update`] so the class is always derived from the same `(running,\r\n/// release)` pair the apply step will act on.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub struct UpdatePlan {\r\n    /// The update class (ADR-0004 §A).\r\n    pub class: UpdateClass,\r\n    /// The release the plan was built for.\r\n    pub release: ReleaseSpec,\r\n}\r\n\r\n/// Classify `release` against `running` and bundle the verdict with it.\r\npub fn plan_update(running: &BrokerAbi, release: ReleaseSpec) -> UpdatePlan {\r\n    UpdatePlan {\r\n        class: classify(running, &release),\r\n        release,\r\n    }\r\n}\r\n\r\n/// The verify-before-handoff gate (C1): authenticate a signed release + confirm\r\n/// the binary bytes, then — and only then — classify it into an [`UpdatePlan`].\r\n///\r\n/// This is the **front door** of an update: ADR-0004 §D makes signing mandatory,\r\n/// so a release is never even classified (let alone applied) until it has passed\r\n/// [`verify_metadata`] (signature + key trust/revocation + channel + expiry +\r\n/// monotonic version) **and** [`verify_artifact`] (the real bytes match the\r\n/// signed digest). A rejected release returns its [`RejectReason`] and **no plan\r\n/// is produced** — there is nothing for [`apply_brain_only`] to act on, so an\r\n/// unverified binary can never reach a handoff ([`REQ-UPD-2`]).\r\n// [impl->REQ-UPD-2]\r\n// [impl->REQ-HAZARD-UPDATE-ROLLBACK]\r\npub fn plan_verified(\r\n    running: &BrokerAbi,\r\n    signed: &SignedRelease,\r\n    artifact: &[u8],\r\n    policy: &VerifyPolicy,\r\n) -> Result<UpdatePlan, RejectReason> {\r\n    let meta = verify_metadata(signed, policy)?;\r\n    verify_artifact(&meta, artifact)?;\r\n    Ok(plan_update(running, meta.spec()))\r\n}\r\n\r\n/// The update-set front door: authenticate a signed multi-platform set, select\r\n/// the recipient's Rust target triple, confirm those bytes match the signed\r\n/// digest, then classify that platform artifact.\r\n// [impl->REQ-UPD-6]\r\npub fn plan_verified_update_set(\r\n    running: &BrokerAbi,\r\n    signed: &SignedUpdateSet,\r\n    platform: &str,\r\n    artifact: &[u8],\r\n    policy: &VerifyPolicy,\r\n) -> Result<UpdatePlan, RejectReason> {\r\n    let meta = verify_update_set_metadata(signed, policy)?;\r\n    let selected = meta.artifact_for(platform)?;\r\n    verify_update_set_artifact(&meta, platform, artifact)?;\r\n    Ok(plan_update(running, selected.spec()))\r\n}\r\n\r\n/// Why a brain-only apply did not complete.\r\n#[derive(Debug)]\r\npub enum ApplyError {\r\n    /// The plan is not brain-only — routing it through the brain-only path would\r\n    /// risk an endpoint, which this class must never do. Raised **before** the\r\n    /// outgoing brain is touched. The caller routes a broker-compatible /\r\n    /// broker-breaking plan through its own (consent-gated, scheduled) path;\r\n    /// brain-only is the only swap C0 performs.\r\n    NotBrainOnly(UpdateClass),\r\n    /// The new brain failed to re-attach (the `relaunch` closure errored). The\r\n    /// endpoint is **untouched** regardless — the broker + its hosted child\r\n    /// persist across a failed re-attach exactly as across a brain crash — so the\r\n    /// caller may retry against the still-live broker.\r\n    Relaunch(io::Error),\r\n}\r\n\r\nimpl std::fmt::Display for ApplyError {\r\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\r\n        match self {\r\n            ApplyError::NotBrainOnly(c) => {\r\n                write!(\r\n                    f,\r\n                    \"apply_brain_only refused a {c:?} plan (would risk an endpoint)\"\r\n                )\r\n            }\r\n            ApplyError::Relaunch(e) => {\r\n                write!(f, \"brain-only relaunch failed (endpoint intact): {e}\")\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nimpl std::error::Error for ApplyError {\r\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\r\n        match self {\r\n            ApplyError::NotBrainOnly(_) => None,\r\n            ApplyError::Relaunch(e) => Some(e),\r\n        }\r\n    }\r\n}\r\n\r\n/// Apply a **brain-only** update ([`REQ-UPD-3`]): swap the brain logic without\r\n/// disturbing any broker-held resource or hosted endpoint.\r\n///\r\n/// This is a deliberate trigger of the M3b handoff path, **not** a new handoff:\r\n/// it snapshots the outgoing brain ([`Brain::snapshot`]), drops it — the exact\r\n/// crash boundary the broker already tolerates (the subscriber vanishes; the\r\n/// child + its buffered output persist) — and re-attaches by calling `relaunch`\r\n/// with the snapshot. `relaunch` stands for \"exec the new binary's brain, which\r\n/// re-attaches via [`Brain::handoff`]\"; in the live daemon it execs the verified\r\n/// new binary, and the returned brain is generation+1 with a fresh `gen_start`.\r\n///\r\n/// The guard is the invariant: a non-brain-only `plan` is refused\r\n/// ([`ApplyError::NotBrainOnly`]) **before** the outgoing brain is dropped, so\r\n/// the brain-only path can never be the thing that cycles an endpoint. Broker\r\n/// touching classes route elsewhere (C2 consent + scheduling).\r\n///\r\n/// The production caller is [`crate::applyhost::apply_staged`] (M5-D7b): the\r\n/// peer pump still stops at *staged + consent-notified* (REQ-UPD-4 gated\r\n/// default), and the explicit user ack (`spt update apply`) drives the\r\n/// re-verify + class-check + brain handoff orchestration through this seam.\r\n// [impl->REQ-UPD-3]\r\npub fn apply_brain_only<F>(\r\n    plan: &UpdatePlan,\r\n    outgoing: Brain,\r\n    relaunch: F,\r\n) -> Result<Brain, ApplyError>\r\nwhere\r\n    F: FnOnce(BrainState) -> io::Result<Brain>,\r\n{\r\n    if plan.class != UpdateClass::BrainOnly {\r\n        // Refuse *before* touching the outgoing brain — no endpoint is at risk.\r\n        return Err(ApplyError::NotBrainOnly(plan.class));\r\n    }\r\n    let state = outgoing.snapshot();\r\n    drop(outgoing); // the crash boundary the broker tolerates: child + log persist\r\n                    // The new binary's brain re-attaches over the existing substrate. A relaunch\r\n                    // failure surfaces to the caller, which may retry against the still-live\r\n                    // broker (the endpoint is untouched regardless — that is the whole point).\r\n    relaunch(state).map_err(ApplyError::Relaunch)\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n    use super::*;\r\n\r\n    // [unit->REQ-UPD-3] same resource ABI + a brain the running broker can host\r\n    // (IPC at or above the floor) ⇒ brain-only: the routine, endpoint-preserving\r\n    // case. A *newer* brain (higher IPC) against the running broker is still\r\n    // brain-only — the versioned IPC accepts a newer brain on an older broker.\r\n    #[test]\r\n    fn classify_brain_only_when_broker_hosts_new_brain() {\r\n        let running = BrokerAbi::current();\r\n        let same_ipc = ReleaseSpec {\r\n            brain_ipc_version: running.ipc_version,\r\n            broker_resource_abi: running.resource_abi,\r\n        };\r\n        assert_eq!(classify(&running, &same_ipc), UpdateClass::BrainOnly);\r\n\r\n        let newer_brain = ReleaseSpec {\r\n            brain_ipc_version: running.ipc_version + 5,\r\n            broker_resource_abi: running.resource_abi,\r\n        };\r\n        assert_eq!(\r\n            classify(&running, &newer_brain),\r\n            UpdateClass::BrainOnly,\r\n            \"a newer brain the running broker still accepts is brain-only\"\r\n        );\r\n    }\r\n\r\n    // [unit->REQ-UPD-3] same resource ABI but the new brain is below the running\r\n    // broker's IPC floor ⇒ broker-compatible: the broker binary must move to host\r\n    // it, but the held-resource type is unchanged so endpoints still survive.\r\n    #[test]\r\n    fn classify_broker_compatible_when_brain_below_floor() {\r\n        // A running broker with a raised floor (it has already moved past v1).\r\n        let running = BrokerAbi {\r\n            ipc_version: 3,\r\n            min_compatible: 3,\r\n            resource_abi: BROKER_RESOURCE_ABI,\r\n        };\r\n        let old_brain = ReleaseSpec {\r\n            brain_ipc_version: 2,\r\n            broker_resource_abi: BROKER_RESOURCE_ABI,\r\n        };\r\n        assert_eq!(\r\n            classify(&running, &old_brain),\r\n            UpdateClass::BrokerCompatible\r\n        );\r","truncated":true,"truncatedBy":"lines","totalLines":529,"totalBytes":14500,"outputLines":313,"outputBytes":14500,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"//! The self-update engine (C0) — the update-class taxonomy and the brain-only\r\n//! zero-interruption swap.\r\n//!\r\n//! ADR-0004 §A splits a self-update into **three classes**, each with its own\r\n//! invariant, decided on two axes against the *running* broker:\r\n//!\r\n//! - **brain-only** — the routine case. The running broker can already host the\r\n//!   release's brain (the versioned-IPC handshake accepts it) and the broker's\r\n//!   held-resource ABI is unchanged, so only the logic swaps. **No endpoint\r\n//!   process terminates or suspends** ([`REQ-UPD-3`], absolute for this class) —\r\n//!   proven E2E over the M3b handoff substrate.\r\n//! - **broker-compatible** — the release ships a newer broker that the running\r\n//!   broker's IPC floor cannot host the new brain against, but the held-resource\r\n//!   *type* is unchanged, so the new broker hot-adopts the existing handles\r\n//!   behind the versioned IPC; endpoints still survive. (Apply path deferred —\r\n//!   no spike yet, ADR-0004 §A.)\r\n//! - **broker-breaking** — the held-resource *type* or an OS PTY/socket API\r\n//!   changed; this needs a *planned endpoint-cycle* and MAY suspend endpoints\r\n//!   with consent + scheduling. [`REQ-UPD-3`] is explicitly weakened here.\r\n//!\r\n//! C0 implements the **classifier** ([`classify`]) and the **brain-only apply**\r\n//! ([`apply_brain_only`]). The apply *triggers a handoff* over the existing M3b\r\n//! substrate ([`crate::brain::Brain::handoff`]) — it does **not** re-implement\r\n//! one (clean-room posture, `M3c-PLAN.md` §C0). The signing/rollback hardening\r\n//! that gates *which* binary is even eligible to swap arrives in C1.\r\n// [impl->REQ-UPD-3]\r\n\r\nuse std::io;\r\n\r\nuse crate::brain::{Brain, BrainState};\r\nuse crate::frame::{IPC_PROTOCOL_VERSION, MIN_COMPATIBLE_VERSION};\r\nuse crate::release::{\r\n    verify_artifact, verify_metadata, verify_update_set_artifact, verify_update_set_metadata,\r\n    RejectReason, SignedRelease, SignedUpdateSet, VerifyPolicy,\r\n};\r\n\r\n/// The held-resource-type ABI of this build's broker — the broker-breaking axis\r\n/// (ADR-0004 §A/§B). Bumped **only** when the *kind* of resource the broker holds\r\n/// changes: a new held-resource type, or a new OS PTY/socket API that changes how\r\n/// an existing handle is carried. A pure logic change (the routine update) never\r\n/// touches this — that is precisely what keeps the routine case *brain-only*.\r\npub const BROKER_RESOURCE_ABI: u32 = 1;\r\n\r\n/// What the currently-running broker provides — the fixed point an incoming\r\n/// release is classified against (ADR-0004 §A). Captured from the live broker,\r\n/// not from this build, because the whole point of a self-update is that the\r\n/// *running* broker may be older than the binary proposing the update.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub struct BrokerAbi {\r\n    /// The IPC protocol version the running broker speaks.\r\n    pub ipc_version: u32,\r\n    /// The oldest brain IPC version the running broker will handshake\r\n    /// (`accept_hello`'s floor). A release brain at or above this can attach to\r\n    /// the running broker as-is.\r\n    pub min_compatible: u32,\r\n    /// The held-resource-type ABI tag ([`BROKER_RESOURCE_ABI`]) — the\r\n    /// broker-breaking axis.\r\n    pub resource_abi: u32,\r\n}\r\n\r\nimpl BrokerAbi {\r\n    /// The ABI of *this* build's broker. In the live daemon the running broker's\r\n    /// ABI is read off the broker over the IPC; this is the value a broker built\r\n    /// from this source reports (and the classifier's stand-in when the running\r\n    /// broker is this same build).\r\n    pub fn current() -> Self {\r\n        BrokerAbi {\r\n            ipc_version: IPC_PROTOCOL_VERSION,\r\n            min_compatible: MIN_COMPATIBLE_VERSION,\r\n            resource_abi: BROKER_RESOURCE_ABI,\r\n        }\r\n    }\r\n}\r\n\r\n/// What a candidate release declares about its broker/brain — the subset the\r\n/// classifier needs (C1 adds version + signature + expiry). Forward-compatible\r\n/// by intent: only the two ADR-0004 §A axes live here.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub struct ReleaseSpec {\r\n    /// The IPC protocol version the release's **brain** speaks.\r\n    pub brain_ipc_version: u32,\r\n    /// The held-resource-type ABI the release's **broker** implements — compared\r\n    /// against the running broker's [`BrokerAbi::resource_abi`] to detect a\r\n    /// broker-breaking change.\r\n    pub broker_resource_abi: u32,\r\n}\r\n\r\n/// The three update classes (ADR-0004 §A), each routed to its own invariant.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub enum UpdateClass {\r\n    /// Swap logic only; the running broker hosts the new brain unchanged. No\r\n    /// endpoint terminates ([`REQ-UPD-3`], absolute). The routine case.\r\n    BrainOnly,\r\n    /// The broker binary hot-swaps behind the versioned IPC (held-resource type\r\n    /// unchanged); endpoints survive. Apply path deferred (no spike — ADR-0004 §A).\r\n    BrokerCompatible,\r\n    /// Held-resource-type / OS-API change; a planned endpoint-cycle is required\r\n    /// and endpoints MAY suspend with consent + scheduling. [`REQ-UPD-3`] weakened.\r\n    BrokerBreaking,\r\n}\r\n\r\n/// Classify a candidate release against the running broker (ADR-0004 §A).\r\n///\r\n/// The decision is purely the two ADR axes:\r\n/// 1. **held-resource ABI** — if the release's broker implements a *different*\r\n///    `resource_abi`, the held-resource type changed: **broker-breaking**,\r\n///    regardless of IPC (a planned endpoint-cycle is unavoidable).\r\n/// 2. **IPC hostability** — with the resource type unchanged, ask whether the\r\n///    *running* broker can host the release's brain. If the brain's IPC version\r\n///    is at or above the running broker's floor, the handshake accepts it and\r\n///    only logic swaps: **brain-only**. Otherwise the broker must move to a build\r\n///    that speaks the new IPC — but since the resource type is unchanged the new\r\n///    broker adopts the existing handles: **broker-compatible**.\r\n// [impl->REQ-UPD-3]\r\npub fn classify(running: &BrokerAbi, release: &ReleaseSpec) -> UpdateClass {\r\n    if release.broker_resource_abi != running.resource_abi {\r\n        return UpdateClass::BrokerBreaking;\r\n    }\r\n    if release.brain_ipc_version >= running.min_compatible {\r\n        UpdateClass::BrainOnly\r\n    } else {\r\n        UpdateClass::BrokerCompatible\r\n    }\r\n}\r\n\r\n/// A classified update: the class plus the release that produced it. Built by\r\n/// [`plan_update`] so the class is always derived from the same `(running,\r\n/// release)` pair the apply step will act on.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub struct UpdatePlan {\r\n    /// The update class (ADR-0004 §A).\r\n    pub class: UpdateClass,\r\n    /// The release the plan was built for.\r\n    pub release: ReleaseSpec,\r\n}\r\n\r\n/// Classify `release` against `running` and bundle the verdict with it.\r\npub fn plan_update(running: &BrokerAbi, release: ReleaseSpec) -> UpdatePlan {\r\n    UpdatePlan {\r\n        class: classify(running, &release),\r\n        release,\r\n    }\r\n}\r\n\r\n/// The verify-before-handoff gate (C1): authenticate a signed release + confirm\r\n/// the binary bytes, then — and only then — classify it into an [`UpdatePlan`].\r\n///\r\n/// This is the **front door** of an update: ADR-0004 §D makes signing mandatory,\r\n/// so a release is never even classified (let alone applied) until it has passed\r\n/// [`verify_metadata`] (signature + key trust/revocation + channel + expiry +\r\n/// monotonic version) **and** [`verify_artifact`] (the real bytes match the\r\n/// signed digest). A rejected release returns its [`RejectReason`] and **no plan\r\n/// is produced** — there is nothing for [`apply_brain_only`] to act on, so an\r\n/// unverified binary can never reach a handoff ([`REQ-UPD-2`]).\r\n// [impl->REQ-UPD-2]\r\n// [impl->REQ-HAZARD-UPDATE-ROLLBACK]\r\npub fn plan_verified(\r\n    running: &BrokerAbi,\r\n    signed: &SignedRelease,\r\n    artifact: &[u8],\r\n    policy: &VerifyPolicy,\r\n) -> Result<UpdatePlan, RejectReason> {\r\n    let meta = verify_metadata(signed, policy)?;\r\n    verify_artifact(&meta, artifact)?;\r\n    Ok(plan_update(running, meta.spec()))\r\n}\r\n\r\n/// The update-set front door: authenticate a signed multi-platform set, select\r\n/// the recipient's Rust target triple, confirm those bytes match the signed\r\n/// digest, then classify that platform artifact.\r\n// [impl->REQ-UPD-6]\r\npub fn plan_verified_update_set(\r\n    running: &BrokerAbi,\r\n    signed: &SignedUpdateSet,\r\n    platform: &str,\r\n    artifact: &[u8],\r\n    policy: &VerifyPolicy,\r\n) -> Result<UpdatePlan, RejectReason> {\r\n    let meta = verify_update_set_metadata(signed, policy)?;\r\n    let selected = meta.artifact_for(platform)?;\r\n    verify_update_set_artifact(&meta, platform, artifact)?;\r\n    Ok(plan_update(running, selected.spec()))\r\n}\r\n\r\n/// Why a brain-only apply did not complete.\r\n#[derive(Debug)]\r\npub enum ApplyError {\r\n    /// The plan is not brain-only — routing it through the brain-only path would\r\n    /// risk an endpoint, which this class must never do. Raised **before** the\r\n    /// outgoing brain is touched. The caller routes a broker-compatible /\r\n    /// broker-breaking plan through its own (consent-gated, scheduled) path;\r\n    /// brain-only is the only swap C0 performs.\r\n    NotBrainOnly(UpdateClass),\r\n    /// The new brain failed to re-attach (the `relaunch` closure errored). The\r\n    /// endpoint is **untouched** regardless — the broker + its hosted child\r\n    /// persist across a failed re-attach exactly as across a brain crash — so the\r\n    /// caller may retry against the still-live broker.\r\n    Relaunch(io::Error),\r\n}\r\n\r\nimpl std::fmt::Display for ApplyError {\r\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\r\n        match self {\r\n            ApplyError::NotBrainOnly(c) => {\r\n                write!(\r\n                    f,\r\n                    \"apply_brain_only refused a {c:?} plan (would risk an endpoint)\"\r\n                )\r\n            }\r\n            ApplyError::Relaunch(e) => {\r\n                write!(f, \"brain-only relaunch failed (endpoint intact): {e}\")\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nimpl std::error::Error for ApplyError {\r\n    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {\r\n        match self {\r\n            ApplyError::NotBrainOnly(_) => None,\r\n            ApplyError::Relaunch(e) => Some(e),\r\n        }\r\n    }\r\n}\r\n\r\n/// Apply a **brain-only** update ([`REQ-UPD-3`]): swap the brain logic without\r\n/// disturbing any broker-held resource or hosted endpoint.\r\n///\r\n/// This is a deliberate trigger of the M3b handoff path, **not** a new handoff:\r\n/// it snapshots the outgoing brain ([`Brain::snapshot`]), drops it — the exact\r\n/// crash boundary the broker already tolerates (the subscriber vanishes; the\r\n/// child + its buffered output persist) — and re-attaches by calling `relaunch`\r\n/// with the snapshot. `relaunch` stands for \"exec the new binary's brain, which\r\n/// re-attaches via [`Brain::handoff`]\"; in the live daemon it execs the verified\r\n/// new binary, and the returned brain is generation+1 with a fresh `gen_start`.\r\n///\r\n/// The guard is the invariant: a non-brain-only `plan` is refused\r\n/// ([`ApplyError::NotBrainOnly`]) **before** the outgoing brain is dropped, so\r\n/// the brain-only path can never be the thing that cycles an endpoint. Broker\r\n/// touching classes route elsewhere (C2 consent + scheduling).\r\n///\r\n/// The production caller is [`crate::applyhost::apply_staged`] (M5-D7b): the\r\n/// peer pump still stops at *staged + consent-notified* (REQ-UPD-4 gated\r\n/// default), and the explicit user ack (`spt update apply`) drives the\r\n/// re-verify + class-check + brain handoff orchestration through this seam.\r\n// [impl->REQ-UPD-3]\r\npub fn apply_brain_only<F>(\r\n    plan: &UpdatePlan,\r\n    outgoing: Brain,\r\n    relaunch: F,\r\n) -> Result<Brain, ApplyError>\r\nwhere\r\n    F: FnOnce(BrainState) -> io::Result<Brain>,\r\n{\r\n    if plan.class != UpdateClass::BrainOnly {\r\n        // Refuse *before* touching the outgoing brain — no endpoint is at risk.\r\n        return Err(ApplyError::NotBrainOnly(plan.class));\r\n    }\r\n    let state = outgoing.snapshot();\r\n    drop(outgoing); // the crash boundary the broker tolerates: child + log persist\r\n                    // The new binary's brain re-attaches over the existing substrate. A relaunch\r\n                    // failure surfaces to the caller, which may retry against the still-live\r\n                    // broker (the endpoint is untouched regardless — that is the whole point).\r\n    relaunch(state).map_err(ApplyError::Relaunch)\r\n}\r\n\r\n#[cfg(test)]\r\nmod tests {\r\n    use super::*;\r\n\r\n    // [unit->REQ-UPD-3] same resource ABI + a brain the running broker can host\r\n    // (IPC at or above the floor) ⇒ brain-only: the routine, endpoint-preserving\r\n    // case. A *newer* brain (higher IPC) against the running broker is still\r\n    // brain-only — the versioned IPC accepts a newer brain on an older broker.\r\n    #[test]\r\n    fn classify_brain_only_when_broker_hosts_new_brain() {\r\n        let running = BrokerAbi::current();\r\n        let same_ipc = ReleaseSpec {\r\n            brain_ipc_version: running.ipc_version,\r\n            broker_resource_abi: running.resource_abi,\r\n        };\r\n        assert_eq!(classify(&running, &same_ipc), UpdateClass::BrainOnly);\r\n\r\n        let newer_brain = ReleaseSpec {\r\n            brain_ipc_version: running.ipc_version + 5,\r\n            broker_resource_abi: running.resource_abi,\r\n        };\r\n        assert_eq!(\r\n            classify(&running, &newer_brain),\r\n            UpdateClass::BrainOnly,\r\n            \"a newer brain the running broker still accepts is brain-only\"\r\n        );\r\n    }\r\n\r\n    // [unit->REQ-UPD-3] same resource ABI but the new brain is below the running\r\n    // broker's IPC floor ⇒ broker-compatible: the broker binary must move to host\r\n    // it, but the held-resource type is unchanged so endpoints still survive.\r\n    #[test]\r\n    fn classify_broker_compatible_when_brain_below_floor() {\r\n        // A running broker with a raised floor (it has already moved past v1).\r\n        let running = BrokerAbi {\r\n            ipc_version: 3,\r\n            min_compatible: 3,\r\n            resource_abi: BROKER_RESOURCE_ABI,\r\n        };\r\n        let old_brain = ReleaseSpec {\r\n            brain_ipc_version: 2,\r\n            broker_resource_abi: BROKER_RESOURCE_ABI,\r\n        };\r\n        assert_eq!(\r\n            classify(&running, &old_brain),\r\n            UpdateClass::BrokerCompatible\r\n        );\r\n    }\n…\n}","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,null,528]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\update.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":529,"totalBytes":14500,"outputLines":313,"outputBytes":14500,"shownRange":{"start":1,"end":313},"nextOffset":314}}},"isError":false,"timestamp":1784247487787}}
{"type":"message","id":"97f1f01f","parentId":"09634095","timestamp":"2026-07-17T00:18:07.854Z","message":{"role":"toolResult","toolCallId":"call_HgCLVY0DO7QBzbXuHax2wm3W|fc_099a7567dadef391016a5974bfba90819b935b27d2d09747d0","toolName":"read","content":[{"type":"text","text":"# Known Hazards\n…\n## Conformance checklist (condensed)\n…\n| # | Invariant | spt-core surface |\n…\n| 7.20 | `spt rc` must forward the SCROLL wheel to the harness — our mouse capture (for right-click paste, 7.18) steals WT's native scroll. Track the harness's mouse-reporting mode from its OUTPUT (DECSET `ESC[?1000/1002/1003h` + `1006h` SGR, and `…l` off; scan survives a split across output chunks) into `MouseMode{enabled,sgr}`; forward `ScrollUp/Down` as an xterm SGR report (`ESC[<64/65;col+1;row+1M`) ONLY when `enabled && sgr`, else drop; Moved/drag/clicks dropped. cfg(windows) only — Unix scrolls natively | `rc.rs` `MouseMode`/`MouseModeScanner`/`scroll_dir`/`scroll_sgr`/`spawn_stdin_reader_events`, unit-only (live mouse = HITL) |\n| 7.21 | Exactly ONE LIVE `controller_writer` per brain↔broker connection + every writer emits an ASCENDING seq stream ⇒ a snap-above consumer over the surviving writer's COMPLETE `[0,end]` replay delivers `[K,end]` with no skip/dup. A brain-restart re-serve double-registered the controller (handoff's `subscribe(from_seq=K)` + the re-serve's `attach_as(sid,0)`) → two writers raced one socket → the consumer's strict reject-gap legacy path saw `got seq 1 want 0` (flaky `attach_survives_target_brain_restart_exactly_once`). FIX (fix #1 \"drop handoff's subscribe\" REVERTED — it's the standalone-resume mechanism): (1) `handoff` seeds `session_cursors` → dedup-below + snap-above (correctness, made complete by the ascending-merge property); (2) `controller_writer` ep…\n| 7.22 | Endpoint-stop / brain-death reaps a brain-LESS perch's orphan detached Psyche via the cmdline-scoped guard — the handle-reap (`stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT (the owning brain is dead, so its `psyche_child` handle died with it) and the brain-START sweep (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host runs the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Fail-safe-decline (pid-alive AND basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES — a missed dup is bounded, a wrong-kill catastrophic). The orphan-leak half of perri F-010xF-015 (the psyche own-copy is the other half, ADR-…\n<!-- [doc->REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP] -->\n| 7.23 | An already-spooled message (WAN-arrived or spooled-while-active) NEVER depends on adapter hook-poll cadence to reach a relay-less spt-hosted endpoint — the daemon drives delivery on the events it owns. TWO daemon triggers feed ONE shared translation-binary inject leg (`spt_daemon::inject::try_spt_hosted_inject`): WAN ingress (`receive_wan` injects before spool — leg 1, `[REQ-WAN-SPT-HOSTED-DELIVERY]`) + the ACTIVE→IDLE edge (`drain_idle_window` claims the pending spool — deferred AND non-deferred — and injects, reusing the hook-poll take/ack so a concurrent `api poll` can't double-deliver — leg 2, `[REQ-MSG-IDLE-EDGE-DRAIN]`). v0.14.3 LAW holds on both: no binary ⇒ SPOOL LOUD (row released intact), never a raw PTY write. Fixes F-023 \"sent but never …\n<!-- [doc->REQ-HAZARD-DELIVERY-STARVATION] -->\n| 7.24 | A DELEGATED live adapter apply never reports success without swapping, and the live-update seam uses ONE parent-aware matcher. `spt_runtime::profile::adapter_parent_matches(session_adapter, parent)` (`split_option().0 == parent`) at ALL three comparators (CLI `adapter_has_live_endpoint`, broker `dispatch_adapter_apply` filter, `select_endpoints_running_adapter`) — no exact `==` against a record name, so a `--adapter cc:ccs` composite endpoint resolves to parent `cc`. `dispatch_adapter_apply` swaps UNCONDITIONALLY (empty-affected early-return removed); `KIND_APPLIED` only after a real swap. Fixes F015B silent no-op (D1 matcher skew dropped every `:profile` endpoint → affected=[] → D2 success-without-swap) | `profile.rs` `adapter_parent_matches`, `bro…\n<!-- [doc->REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP] -->\n| 7.25 | A perch pinned to a DEAD session self-heals; a LIVE-owner rotation still refuses. `authenticate()` (auth.rs): sid mismatch AND recorded owner pid DEAD (`proc::is_process_alive`) → accept caller sid + RE-PIN (`mutate_info` rotate `session_id` + LOUD `SESSION_REPIN`) — ADR-0032 layer 2, same trust as `establish_perch`'s dead-owner rebind. LIVE-owner mismatch (a `/clear`/`/compact` live-pid rotation) STILL refuses (squat, unwidened — live-rotation proof is the adapter's layer-1 job, `[REQ-BOUNDARY-ROTATION-CREDENTIAL]`; parent_pid ancestry = parked layer 3). ADDITIVE to token auth (branch fires only on no/failed token AND sid mismatch AND owner dead). Fixes the F-024C/D permanent-strand wedge (one lost boundary rotation → every id-scoped hook incl boun…\n<!-- [doc->REQ-HAZARD-SESSION-PIN-WEDGE] -->\n| 7.26 | Concurrent first-touch of ONE fresh BranchStore all-Ok — the non-atomic `git init` (template-hook copy) + `config.lock` race must never strand a first-toucher. `open_or_init` → `init_bare_tolerant` (bounded backoff-retry on the init collision that desyncs racers + open-after-lose on a racer-created HEAD) + `config_set_locked_retry` (bounded `config.lock`-aware retry; `core.autocrlf` idempotent). Convergence guaranteed: a lone re-init on a partial dir completes. Fixes the G3-gate pump.rs:442 flake | `branchstore.rs` `open_or_init`/`init_bare_tolerant`/`config_set_locked_retry`, unit `concurrent_open_or_init_on_one_fresh_store_all_ok` (N=8, RED-FIRST) |\n| 7.27 | A control/viewer stamp never outlives its session — every teardown path clears what attach stamped. `/exit` kills the CHILD (endpoint suspends on child death) so the teardown runs the REAP path (exit-waiter → `sessions.remove`), never the controller-detach path — the only pre-fix stamp-clear. The exit-waiter now calls `OutputLog::stamp_reaped()` = `set_driven_by(None)`+`set_controlled(false)`+`set_viewer_count(0)` on reap (broker single-writer, unconditional/idempotent, race-free — dead session ⇒ no concurrent re-stamp). Fixes F-026 #2 (hall-a latched `controlled:true` + gossiped `controller_node=self` for hours after `/exit`). Distinct from 7.15 (sessionless reconcile heal) | `broker.rs` `OutputLog::stamp_reaped` + exit-waiter reap call, int `contr…\n| 7.28 | A relative manifest path naming an endpoint-read/written location resolves against the ENDPOINT's cwd (`info.cwd`, read at USE time), never the daemon's process cwd (one daemon hosts many cwds). commune_dir/signoff_dir resolve at ingest: absolute as-is, relative+cwd joined, relative+no-cwd SKIPPED loud-once. Project routing owlery-gated (owlery-internal anchor → no `p-<project>`, live slice still commits; else `project_id_for_dir` parity). {cwd}-templated (digest/history) + daemon-absolute-filled (session-role/translation) keys already anchored. Fixes F-026 SI-1 (pristine BranchStore box-wide; abs-only test fixtures masked the relative leg) | `lifecycle.rs` `resolve_endpoint_drop_dir`/`is_owlery_internal`/`warn_no_cwd_once`/`pulse_tick`, `ingest.rs`…\n| 7.29 | Control/viewer stamps CONVERGE to broker session-table truth (upward), not merely edge-trigger — the companion to 7.27's downward edge-clear. A picker-created endpoint's spawn `set_controlled(true)` fires PRE-bind (no perch → `mutate_info` NotFound swallowed); the adapter binds `controlled:false` and no edge re-stamps → uncontrolled-forever-while-driven (hall-b). Broker (single-writer) re-asserts each live session's stamps DIVERGENCE-GATED (write only on diff, no fsync storm) on the `KIND_SESSIONS` poll (bounded window = reconcile cadence, no new timer); truth snapshotted under the log lock, writes OFF it. Event-on-input insufficient (idle controlled sessions). Fixes F-026 stamp-gap | `broker.rs` `stamp_divergence`/`converge_perch_stamps`/`has_contr…\n| 7.30 | A Psyche failure of ANY shape never removes/alters the parent endpoint's ready/hosted state — a Psyche is a bounded per-event turn, so there is no \"resident gone\" signal; a turn failure stamps `psyche_host_error` ONLY, never `status`/ready. Residency machinery (`confirm_residency_or_unhost`) + the teardown-on-psyche-trouble deleted. Fixes the adapter v0.13.2 field brick (shim exit → residency teardown → ready removed → permanent `cli-gate-not-hosted`) | `livehost.rs` residency/reap deletions, `lifecycle.rs` `run_psyche_event_turn` psyche-fields-only stamp + `first_turn_psyche_context` non-empty, int (hall-bf shape: failing psyche → parent stays deliverable, no churn) |\n<!-- [doc->REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION] -->\n| 7.38 | Every physical broker-conn write bounded + cancelable + poison-on-failure (independent watchdog aborter; poisoned conn never reused; SendHalf never leaves the conn object) | broker `conn.rs` `BrokerConn`, all write sites (`controller_writer`/`viewer_writer`/`send_frame`/nethost), int `brain_decouple.rs` |\n| 7.31 | The Psyche failure budget counts REAL per-event attempts — every bounded per-event turn feeds a consecutive-N budget (default 3), so per-event churn a resident rate-guard was blind to now counts by construction. Fixes hall-bf's ~12/min re-host churn that ran invisibly (boot records weren't ledger boundaries to the old guard) | `lifecycle.rs` `note_turn_outcome`/`psyche_turn_strikes_exhausted` (stamps `psyche_host_error` on exhaustion, no `status` de-stamp), unit synthetic failure-loop trips the budget (RED-first control) |\n<!-- [doc->REQ-HAZARD-THRASH-GUARD-BLIND] -->\n<!-- [doc->REQ-HAZARD-STORE-INIT-RACE] -->\n\n\n[Some lines truncated to 768 chars]"}],"details":{"displayContent":{"text":"# Known Hazards\n…\n## Conformance checklist (condensed)\n…\n| # | Invariant | spt-core surface |\n…\n| 7.20 | `spt rc` must forward the SCROLL wheel to the harness — our mouse capture (for right-click paste, 7.18) steals WT's native scroll. Track the harness's mouse-reporting mode from its OUTPUT (DECSET `ESC[?1000/1002/1003h` + `1006h` SGR, and `…l` off; scan survives a split across output chunks) into `MouseMode{enabled,sgr}`; forward `ScrollUp/Down` as an xterm SGR report (`ESC[<64/65;col+1;row+1M`) ONLY when `enabled && sgr`, else drop; Moved/drag/clicks dropped. cfg(windows) only — Unix scrolls natively | `rc.rs` `MouseMode`/`MouseModeScanner`/`scroll_dir`/`scroll_sgr`/`spawn_stdin_reader_events`, unit-only (live mouse = HITL) |\r\n| 7.21 | Exactly ONE LIVE `controller_writer` per brain↔broker connection + every writer emits an ASCENDING seq stream ⇒ a snap-above consumer over the surviving writer's COMPLETE `[0,end]` replay delivers `[K,end]` with no skip/dup. A brain-restart re-serve double-registered the controller (handoff's `subscribe(from_seq=K)` + the re-serve's `attach_as(sid,0)`) → two writers raced one socket → the consumer's strict reject-gap legacy path saw `got seq 1 want 0` (flaky `attach_survives_target_brain_restart_exactly_once`). FIX (fix #1 \"drop handoff's subscribe\" REVERTED — it's the standalone-resume mechanism): (1) `handoff` seeds `session_cursors` → dedup-below + snap-above (correctness, made complete by the ascending-merge property); (2) `controller_writer` ep…\n| 7.22 | Endpoint-stop / brain-death reaps a brain-LESS perch's orphan detached Psyche via the cmdline-scoped guard — the handle-reap (`stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT (the owning brain is dead, so its `psyche_child` handle died with it) and the brain-START sweep (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host runs the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Fail-safe-decline (pid-alive AND basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES — a missed dup is bounded, a wrong-kill catastrophic). The orphan-leak half of perri F-010xF-015 (the psyche own-copy is the other half, ADR-…\n<!-- [doc->REQ-HAZARD-STOP-PATH-PSYCHE-ORPHAN-REAP] -->\r\n| 7.23 | An already-spooled message (WAN-arrived or spooled-while-active) NEVER depends on adapter hook-poll cadence to reach a relay-less spt-hosted endpoint — the daemon drives delivery on the events it owns. TWO daemon triggers feed ONE shared translation-binary inject leg (`spt_daemon::inject::try_spt_hosted_inject`): WAN ingress (`receive_wan` injects before spool — leg 1, `[REQ-WAN-SPT-HOSTED-DELIVERY]`) + the ACTIVE→IDLE edge (`drain_idle_window` claims the pending spool — deferred AND non-deferred — and injects, reusing the hook-poll take/ack so a concurrent `api poll` can't double-deliver — leg 2, `[REQ-MSG-IDLE-EDGE-DRAIN]`). v0.14.3 LAW holds on both: no binary ⇒ SPOOL LOUD (row released intact), never a raw PTY write. Fixes F-023 \"sent but never …\n<!-- [doc->REQ-HAZARD-DELIVERY-STARVATION] -->\r\n| 7.24 | A DELEGATED live adapter apply never reports success without swapping, and the live-update seam uses ONE parent-aware matcher. `spt_runtime::profile::adapter_parent_matches(session_adapter, parent)` (`split_option().0 == parent`) at ALL three comparators (CLI `adapter_has_live_endpoint`, broker `dispatch_adapter_apply` filter, `select_endpoints_running_adapter`) — no exact `==` against a record name, so a `--adapter cc:ccs` composite endpoint resolves to parent `cc`. `dispatch_adapter_apply` swaps UNCONDITIONALLY (empty-affected early-return removed); `KIND_APPLIED` only after a real swap. Fixes F015B silent no-op (D1 matcher skew dropped every `:profile` endpoint → affected=[] → D2 success-without-swap) | `profile.rs` `adapter_parent_matches`, `bro…\n<!-- [doc->REQ-HAZARD-ADAPTER-APPLY-SILENT-NOOP] -->\r\n| 7.25 | A perch pinned to a DEAD session self-heals; a LIVE-owner rotation still refuses. `authenticate()` (auth.rs): sid mismatch AND recorded owner pid DEAD (`proc::is_process_alive`) → accept caller sid + RE-PIN (`mutate_info` rotate `session_id` + LOUD `SESSION_REPIN`) — ADR-0032 layer 2, same trust as `establish_perch`'s dead-owner rebind. LIVE-owner mismatch (a `/clear`/`/compact` live-pid rotation) STILL refuses (squat, unwidened — live-rotation proof is the adapter's layer-1 job, `[REQ-BOUNDARY-ROTATION-CREDENTIAL]`; parent_pid ancestry = parked layer 3). ADDITIVE to token auth (branch fires only on no/failed token AND sid mismatch AND owner dead). Fixes the F-024C/D permanent-strand wedge (one lost boundary rotation → every id-scoped hook incl boun…\n<!-- [doc->REQ-HAZARD-SESSION-PIN-WEDGE] -->\r\n| 7.26 | Concurrent first-touch of ONE fresh BranchStore all-Ok — the non-atomic `git init` (template-hook copy) + `config.lock` race must never strand a first-toucher. `open_or_init` → `init_bare_tolerant` (bounded backoff-retry on the init collision that desyncs racers + open-after-lose on a racer-created HEAD) + `config_set_locked_retry` (bounded `config.lock`-aware retry; `core.autocrlf` idempotent). Convergence guaranteed: a lone re-init on a partial dir completes. Fixes the G3-gate pump.rs:442 flake | `branchstore.rs` `open_or_init`/`init_bare_tolerant`/`config_set_locked_retry`, unit `concurrent_open_or_init_on_one_fresh_store_all_ok` (N=8, RED-FIRST) |\r\n| 7.27 | A control/viewer stamp never outlives its session — every teardown path clears what attach stamped. `/exit` kills the CHILD (endpoint suspends on child death) so the teardown runs the REAP path (exit-waiter → `sessions.remove`), never the controller-detach path — the only pre-fix stamp-clear. The exit-waiter now calls `OutputLog::stamp_reaped()` = `set_driven_by(None)`+`set_controlled(false)`+`set_viewer_count(0)` on reap (broker single-writer, unconditional/idempotent, race-free — dead session ⇒ no concurrent re-stamp). Fixes F-026 #2 (hall-a latched `controlled:true` + gossiped `controller_node=self` for hours after `/exit`). Distinct from 7.15 (sessionless reconcile heal) | `broker.rs` `OutputLog::stamp_reaped` + exit-waiter reap call, int `contr…\n| 7.28 | A relative manifest path naming an endpoint-read/written location resolves against the ENDPOINT's cwd (`info.cwd`, read at USE time), never the daemon's process cwd (one daemon hosts many cwds). commune_dir/signoff_dir resolve at ingest: absolute as-is, relative+cwd joined, relative+no-cwd SKIPPED loud-once. Project routing owlery-gated (owlery-internal anchor → no `p-<project>`, live slice still commits; else `project_id_for_dir` parity). {cwd}-templated (digest/history) + daemon-absolute-filled (session-role/translation) keys already anchored. Fixes F-026 SI-1 (pristine BranchStore box-wide; abs-only test fixtures masked the relative leg) | `lifecycle.rs` `resolve_endpoint_drop_dir`/`is_owlery_internal`/`warn_no_cwd_once`/`pulse_tick`, `ingest.rs`…\n| 7.29 | Control/viewer stamps CONVERGE to broker session-table truth (upward), not merely edge-trigger — the companion to 7.27's downward edge-clear. A picker-created endpoint's spawn `set_controlled(true)` fires PRE-bind (no perch → `mutate_info` NotFound swallowed); the adapter binds `controlled:false` and no edge re-stamps → uncontrolled-forever-while-driven (hall-b). Broker (single-writer) re-asserts each live session's stamps DIVERGENCE-GATED (write only on diff, no fsync storm) on the `KIND_SESSIONS` poll (bounded window = reconcile cadence, no new timer); truth snapshotted under the log lock, writes OFF it. Event-on-input insufficient (idle controlled sessions). Fixes F-026 stamp-gap | `broker.rs` `stamp_divergence`/`converge_perch_stamps`/`has_contr…\n| 7.30 | A Psyche failure of ANY shape never removes/alters the parent endpoint's ready/hosted state — a Psyche is a bounded per-event turn, so there is no \"resident gone\" signal; a turn failure stamps `psyche_host_error` ONLY, never `status`/ready. Residency machinery (`confirm_residency_or_unhost`) + the teardown-on-psyche-trouble deleted. Fixes the adapter v0.13.2 field brick (shim exit → residency teardown → ready removed → permanent `cli-gate-not-hosted`) | `livehost.rs` residency/reap deletions, `lifecycle.rs` `run_psyche_event_turn` psyche-fields-only stamp + `first_turn_psyche_context` non-empty, int (hall-bf shape: failing psyche → parent stays deliverable, no churn) |\r\n<!-- [doc->REQ-HAZARD-PSYCHE-RESIDENCY-EXPECTATION] -->\r\n| 7.38 | Every physical broker-conn write bounded + cancelable + poison-on-failure (independent watchdog aborter; poisoned conn never reused; SendHalf never leaves the conn object) | broker `conn.rs` `BrokerConn`, all write sites (`controller_writer`/`viewer_writer`/`send_frame`/nethost), int `brain_decouple.rs` |\r\n| 7.31 | The Psyche failure budget counts REAL per-event attempts — every bounded per-event turn feeds a consecutive-N budget (default 3), so per-event churn a resident rate-guard was blind to now counts by construction. Fixes hall-bf's ~12/min re-host churn that ran invisibly (boot records weren't ledger boundaries to the old guard) | `lifecycle.rs` `note_turn_outcome`/`psyche_turn_strikes_exhausted` (stamps `psyche_host_error` on exhaustion, no `status` de-stamp), unit synthetic failure-loop trips the budget (RED-first control) |\r\n<!-- [doc->REQ-HAZARD-THRASH-GUARD-BLIND] -->\r\n<!-- [doc->REQ-HAZARD-STORE-INIT-RACE] -->\r\n","startLine":1,"lineNumbers":[1,null,683,null,685,null,734,735,736,737,738,739,740,741,742,743,744,745,746,747,748,749,750,751,752,753,754]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\KNOWN-HAZARDS.md"},"limits":{"columnTruncated":{"maxColumn":768}}}},"isError":false,"timestamp":1784247487854}}
{"type":"message","id":"6a7777a8","parentId":"97f1f01f","timestamp":"2026-07-17T00:18:08.040Z","message":{"role":"toolResult","toolCallId":"call_cIDhnUurhTZjqZKDR8AXKe7d|fc_099a7567dadef391016a5974bfba88819baca1fb4397ea5f24","toolName":"read","content":[{"type":"text","text":"struct DaemonStatusJson {\n…\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    project_index: Option<spt_daemon::projwriter::IndexWriterStats>,\n    /// MESH-RECOVERY (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): the pump's\n    /// reachability snapshot — live peer count, last successful dial, last\n    /// admitted registry update, all-peer-failure window, per-peer failing\n    /// stages. ADDITIVE (an N-1 reader ignores it); `None` = no snapshot on\n    /// this home yet.\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    peer_health: Option<spt_daemon::pump::health::PumpHealth>,\n    /// The rendered verdict over `peer_health`: `idle` / `healthy` /\n    /// `connecting` / `degraded`. Omitted with it.\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    peer_health_verdict: Option<String>,\n}\n\n/// One member subnet's count row in the daemon status view.\n// [impl->REQ-CLI-JSON]\n#[derive(serde::Serialize)]\nstruct DaemonSubnetJson {\n    name: String,\n    nodes: usize,\n    endpoints: usize,\n}\n\n/// F-025 running-image surface: compose the `daemon status` line reporting the\n/// version the BROKER process is actually executing beside the installed CLI\n/// version. `running` is the broker's self-reported compiled version (`None` = the\n/// broker predates the self-report IPC, i.e. it is definitely older than this\n/// build). A mismatch means a `spt update apply` landed on disk but the surviving\n/// broker still runs the previous image — daemon-coordinated features keep running\n/// the old version until the daemon restarts. Public wording, no internal codes.\n/// Pure — unit-tested.\n// [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\nfn render_broker_image_line(running: Option<&str>, installed: &str) -> String {\n    match running {\n        Some(v) if v == installed => format!(\"broker image: {v} (matches installed)\"),\n        Some(v) => format!(\n            \"broker image: {v} (running) — installed is {installed}; the daemon is still on the \\\n             previous version, so daemon-coordinated features run {v} until it restarts. Restart \\\n             with `spt daemon stop` then `spt daemon start`.\"\n        ),\n        None => format!(\n            \"broker image: not reported (the running daemon predates version reporting, or could \\\n             not be queried) — if it is older than the installed {installed}, restart with \\\n             `spt daemon stop` then `spt daemon start` to load {installed}.\"\n        ),\n    }\n}\n\n/// LIFECYCLE-TRUTH W2 observability rider: the `daemon status` line reporting\n/// brain-subscriber stall-evicts (a controller detached because its brain conn was\n/// suspended / black-holed). `None` = the broker couldn't be queried / is too old\n/// (silent — no line). `Some(0)` renders a healthy line so \"none\" is an explicit\n/// fact, not an absence; `Some(n>0)` names the count + the most-recent time so a\n/// brain that never returned is diagnosable. Public wording, no internal codes.\n/// Pure — unit-tested.\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n/// Peer-reachability health line (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH):\n/// the answer to \"can I reach anyone?\" — the question the 2026-07-10 sequester\n/// proved `net_up`/heartbeat/counts all dodge. Degraded NAMES the stage the\n/// dials die in; healthy requires a live peer connection (real progress), so\n/// the incident fingerprint (all dials failing, everything else green) renders\n/// DEGRADED here. `None` = no health snapshot (older daemon / no pump yet) or\n/// a solo node with nobody to reach — silent, not alarming. Pure for tests.\n// [impl->REQ-PUMP-STAGE-TRUTH]\nfn render_peer_health_line(\n    health: Option<&spt_daemon::pump::health::PumpHealth>,\n    now: u64,\n) -> Option<String> {\n    use spt_daemon::pump::health::HealthVerdict;\n…\n}\n\n[Showing lines 4179-4248 of 19062. Use :4249 to continue]"}],"details":{"truncation":{"content":"    #[serde(default, skip_serializing_if = \"Option::is_none\")]\r\n    project_index: Option<spt_daemon::projwriter::IndexWriterStats>,\r\n    /// MESH-RECOVERY (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): the pump's\r\n    /// reachability snapshot — live peer count, last successful dial, last\r\n    /// admitted registry update, all-peer-failure window, per-peer failing\r\n    /// stages. ADDITIVE (an N-1 reader ignores it); `None` = no snapshot on\r\n    /// this home yet.\r\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\r\n    peer_health: Option<spt_daemon::pump::health::PumpHealth>,\r\n    /// The rendered verdict over `peer_health`: `idle` / `healthy` /\r\n    /// `connecting` / `degraded`. Omitted with it.\r\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\r\n    peer_health_verdict: Option<String>,\r\n}\r\n\r\n/// One member subnet's count row in the daemon status view.\r\n// [impl->REQ-CLI-JSON]\r\n#[derive(serde::Serialize)]\r\nstruct DaemonSubnetJson {\r\n    name: String,\r\n    nodes: usize,\r\n    endpoints: usize,\r\n}\r\n\r\n/// F-025 running-image surface: compose the `daemon status` line reporting the\r\n/// version the BROKER process is actually executing beside the installed CLI\r\n/// version. `running` is the broker's self-reported compiled version (`None` = the\r\n/// broker predates the self-report IPC, i.e. it is definitely older than this\r\n/// build). A mismatch means a `spt update apply` landed on disk but the surviving\r\n/// broker still runs the previous image — daemon-coordinated features keep running\r\n/// the old version until the daemon restarts. Public wording, no internal codes.\r\n/// Pure — unit-tested.\r\n// [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\r\nfn render_broker_image_line(running: Option<&str>, installed: &str) -> String {\r\n    match running {\r\n        Some(v) if v == installed => format!(\"broker image: {v} (matches installed)\"),\r\n        Some(v) => format!(\r\n            \"broker image: {v} (running) — installed is {installed}; the daemon is still on the \\\r\n             previous version, so daemon-coordinated features run {v} until it restarts. Restart \\\r\n             with `spt daemon stop` then `spt daemon start`.\"\r\n        ),\r\n        None => format!(\r\n            \"broker image: not reported (the running daemon predates version reporting, or could \\\r\n             not be queried) — if it is older than the installed {installed}, restart with \\\r\n             `spt daemon stop` then `spt daemon start` to load {installed}.\"\r\n        ),\r\n    }\r\n}\r\n\r\n/// LIFECYCLE-TRUTH W2 observability rider: the `daemon status` line reporting\r\n/// brain-subscriber stall-evicts (a controller detached because its brain conn was\r\n/// suspended / black-holed). `None` = the broker couldn't be queried / is too old\r\n/// (silent — no line). `Some(0)` renders a healthy line so \"none\" is an explicit\r\n/// fact, not an absence; `Some(n>0)` names the count + the most-recent time so a\r\n/// brain that never returned is diagnosable. Public wording, no internal codes.\r\n/// Pure — unit-tested.\r\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\r\n/// Peer-reachability health line (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH):\r\n/// the answer to \"can I reach anyone?\" — the question the 2026-07-10 sequester\r\n/// proved `net_up`/heartbeat/counts all dodge. Degraded NAMES the stage the\r\n/// dials die in; healthy requires a live peer connection (real progress), so\r\n/// the incident fingerprint (all dials failing, everything else green) renders\r\n/// DEGRADED here. `None` = no health snapshot (older daemon / no pump yet) or\r\n/// a solo node with nobody to reach — silent, not alarming. Pure for tests.\r\n// [impl->REQ-PUMP-STAGE-TRUTH]\r\nfn render_peer_health_line(\r\n    health: Option<&spt_daemon::pump::health::PumpHealth>,\r\n    now: u64,\r\n) -> Option<String> {\r\n    use spt_daemon::pump::health::HealthVerdict;\r","truncated":true,"truncatedBy":"lines","totalLines":14884,"totalBytes":3861,"outputLines":70,"outputBytes":3861,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"struct DaemonStatusJson {\n…\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\r\n    project_index: Option<spt_daemon::projwriter::IndexWriterStats>,\r\n    /// MESH-RECOVERY (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): the pump's\r\n    /// reachability snapshot — live peer count, last successful dial, last\r\n    /// admitted registry update, all-peer-failure window, per-peer failing\r\n    /// stages. ADDITIVE (an N-1 reader ignores it); `None` = no snapshot on\r\n    /// this home yet.\r\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\r\n    peer_health: Option<spt_daemon::pump::health::PumpHealth>,\r\n    /// The rendered verdict over `peer_health`: `idle` / `healthy` /\r\n    /// `connecting` / `degraded`. Omitted with it.\r\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\r\n    peer_health_verdict: Option<String>,\r\n}\r\n\r\n/// One member subnet's count row in the daemon status view.\r\n// [impl->REQ-CLI-JSON]\r\n#[derive(serde::Serialize)]\r\nstruct DaemonSubnetJson {\r\n    name: String,\r\n    nodes: usize,\r\n    endpoints: usize,\r\n}\r\n\r\n/// F-025 running-image surface: compose the `daemon status` line reporting the\r\n/// version the BROKER process is actually executing beside the installed CLI\r\n/// version. `running` is the broker's self-reported compiled version (`None` = the\r\n/// broker predates the self-report IPC, i.e. it is definitely older than this\r\n/// build). A mismatch means a `spt update apply` landed on disk but the surviving\r\n/// broker still runs the previous image — daemon-coordinated features keep running\r\n/// the old version until the daemon restarts. Public wording, no internal codes.\r\n/// Pure — unit-tested.\r\n// [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\r\nfn render_broker_image_line(running: Option<&str>, installed: &str) -> String {\r\n    match running {\r\n        Some(v) if v == installed => format!(\"broker image: {v} (matches installed)\"),\r\n        Some(v) => format!(\r\n            \"broker image: {v} (running) — installed is {installed}; the daemon is still on the \\\r\n             previous version, so daemon-coordinated features run {v} until it restarts. Restart \\\r\n             with `spt daemon stop` then `spt daemon start`.\"\r\n        ),\r\n        None => format!(\r\n            \"broker image: not reported (the running daemon predates version reporting, or could \\\r\n             not be queried) — if it is older than the installed {installed}, restart with \\\r\n             `spt daemon stop` then `spt daemon start` to load {installed}.\"\r\n        ),\r\n    }\r\n}\r\n\r\n/// LIFECYCLE-TRUTH W2 observability rider: the `daemon status` line reporting\r\n/// brain-subscriber stall-evicts (a controller detached because its brain conn was\r\n/// suspended / black-holed). `None` = the broker couldn't be queried / is too old\r\n/// (silent — no line). `Some(0)` renders a healthy line so \"none\" is an explicit\r\n/// fact, not an absence; `Some(n>0)` names the count + the most-recent time so a\r\n/// brain that never returned is diagnosable. Public wording, no internal codes.\r\n/// Pure — unit-tested.\r\n// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\r\n/// Peer-reachability health line (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH):\r\n/// the answer to \"can I reach anyone?\" — the question the 2026-07-10 sequester\r\n/// proved `net_up`/heartbeat/counts all dodge. Degraded NAMES the stage the\r\n/// dials die in; healthy requires a live peer connection (real progress), so\r\n/// the incident fingerprint (all dials failing, everything else green) renders\r\n/// DEGRADED here. `None` = no health snapshot (older daemon / no pump yet) or\r\n/// a solo node with nobody to reach — silent, not alarming. Pure for tests.\r\n// [impl->REQ-PUMP-STAGE-TRUTH]\r\nfn render_peer_health_line(\r\n    health: Option<&spt_daemon::pump::health::PumpHealth>,\r\n    now: u64,\r\n) -> Option<String> {\r\n    use spt_daemon::pump::health::HealthVerdict;\r\n…\n}","startLine":4139,"lineNumbers":[4139,null,4179,4180,4181,4182,4183,4184,4185,4186,4187,4188,4189,4190,4191,4192,4193,4194,4195,4196,4197,4198,4199,4200,4201,4202,4203,4204,4205,4206,4207,4208,4209,4210,4211,4212,4213,4214,4215,4216,4217,4218,4219,4220,4221,4222,4223,4224,4225,4226,4227,4228,4229,4230,4231,4232,4233,4234,4235,4236,4237,4238,4239,4240,4241,4242,4243,4244,4245,4246,4247,4248,null,4282]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\cli.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":19062,"totalBytes":3861,"outputLines":70,"outputBytes":3861,"shownRange":{"start":4179,"end":4248},"nextOffset":4249}}},"isError":false,"timestamp":1784247488040}}
{"type":"message","id":"e537c6d6","parentId":"6a7777a8","timestamp":"2026-07-17T00:18:08.042Z","message":{"role":"toolResult","toolCallId":"call_993FKAayiPTuticZn9gEIYJo|fc_099a7567dadef391016a5974bfba98819b83dcd7ebc703e1a4","toolName":"grep","content":[{"type":"text","text":"# docs/\n## KNOWN-HAZARDS.md\n 667|- **Failure (paid-for, hertz field RCA 2026-07-16 — 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply — same `applyhost.rs` path) launches a fresh `dispatch::run_dispatch_loop` whose EMPTY per-process `claimed` set enumerates every broker-held peer stream. `NetShared.streams` has NO removal path (`StreamLog::finish` only marks) and the claim condition has no finished filter → the fresh dispatcher replays FINISHED historical attach streams. A repl...\n*668|- **Invariant:** redispatch eligibility is LIFECYCLE-gated: a finished/terminal stream row is retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach); classification identity is restart-durable and independent of the evictable data ring (immutable opener fact pinned per stream until close); claims are retryable on transient worker-setup failure and terminal on terminal outcomes (no hot-loop). The legitimate same-`by` successor re-take after a brain restart still s...\n 669|- **spt-core mapping:** `spt-daemon/src/dispatch.rs` `run_dispatch_loop` claim condition + `peek_first_line` classification + worker spawn; `spt-daemon/src/nethost.rs` `NetShared.streams` insert/finish + `DEFAULT_STREAM_RING_CHUNKS`; `spt-daemon/src/applyhost.rs` (the brain-cycle trigger).\n*670|- **Source:** REDISPATCH-TRUTH W1 (hertz source-level RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`, doyle seam-verified; ADR-0038; distinct from the CLOSED 0.30.x session-cursor resume-steal saga).\n 671|\n 672|---\n 673|\n## NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md\n 159|> (Field instances this milestone, all live-SENT, no spool copy, unrecoverable:\n*160|> todlando's W1 gate report HEAD-TRUNCATED at doyle; a flynn EVENT fully EMPTY.)\n 161|\n 162|Root: the idle-inject TYPED delivery leg eats HEAD bytes\n 163|nondeterministically — spool rows complete (1669B) vs ~322B received suffix; mid-turn poll\n## PUMP-W2-DESIGN.md\n 2|\n*3|`REQ-PUMP-PEER-ISOLATION`: concurrent non-blocking dials + per-peer fault isolation on the D4c presence-event seam. **Design-only** (no build; W1 gates first, box is deployah's for musl CI). For doyle's design-check before mint+build.\n 4|\n 5|## Premise check (CONTEXT / ADRs)\n 6|- **D4c presence seam is the sanctioned substrate.** `PresenceLog` (nethost.rs:418, ring `DEFAULT_STREAM_RING_CHUNKS`, cursor-resumable, gapless exactly-once, survives brain restart — nethost.rs:279) already carries `PRESENCE_CONNECTED` on every `register_conn` (nethost.rs:458-461). `NetPresenceEvent.kind` is a **string, explicitly so a newer broker can add kinds an older brain tolerates** (msg.rs:904) — adding a dial-outcome kind is the designed forward-compat path, not a wire break.\n## PUMP-W2-DISPATCH-RULING.md\n 2|\n*3|Rules the 5 forks in `docs/PUMP-W2-SCOPING.md` (todlando's sketch @e965bd9). **W2 = REQ-PUMP-PEER-ISOLATION, MANDATORY** (operator ruling 2026-07-08) — concurrent non-blocking dials + per-peer fault isolation. Activate `required_stages = [\"impl\",\"unit\",\"int\"]` at work-start. **W1 gates first** (fast stall-fix, still root-locked on the empirical >30s); W2 is the full wave after — NOT contingent on being the stall root ([[dont-solo-across-role-lines]]: doyle rules design, todlando builds, doyle gates, ...\n 4|\n 5|## Model — ENDORSE Option A (D4c-native)\n 6|The pump subscribes the existing `NetPresenceEvent` stream (cursor-resumable, gapless, exactly-once — nethost.rs:1289, brain.rs:825/1431), submits dials non-blocking, and runs each peer's `peer_step` when its conn-ready event arrives. **No new async substrate** — re-purpose D4c. Correct call; the broker is already async tokio+iroh and dials concurrently (nethost.rs:417).\n## RELEASE-RUNBOOK.md\n 52|     with no gap. Inserting the new heading with an editor whose anchor spans the\n*53|     previous heading can silently *consume* that heading (v0.34.0 did this to\n 54|     `## [0.33.0]`), folding the prior release's body into the new section — and\n 55|     since the body is published verbatim, the release notes then carry two\n 56|     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n\n# .git/logs/\n## HEAD\n 926|42262e6d7d12dee8470df0f82f40ce8d10153ca3 159085754b1aeba397e32e89d4dd25b79fe0a7dd Reavo End <decidel@gmail.com> 1782393604 -0700\tcommit: feat(manifest): W1 — adapter-static subst keys + translation command (v0.16.0)\n*927|159085754b1aeba397e32e89d4dd25b79fe0a7dd fedc6ba58141b5e6a20a84e7c909ee15d4664ca5 Reavo End <decidel@gmail.com> 1782393746 -0700\tcommit: docs(v0.16.0): ratify translation command subst = adapter-static (W1 gate)\n 928|fedc6ba58141b5e6a20a84e7c909ee15d4664ca5 6db75f9d782845226c5b2070e930bd2e643d83d9 Reavo End <decidel@gmail.com> 1782395784 -0700\tcommit: feat(update): W2 — composite [update.post] delegated post-step (v0.16.0)\n 929|6db75f9d782845226c5b2070e930bd2e643d83d9 898401c1677c3749ca810b9e67e8e9b7252f6e7c Reavo End <decidel@gmail.com> 1782395914 -0700\tcommit: docs(v0.16.0): ratify W2 — self_verifies attestation-only + sentinel token\n 930|898401c1677c3749ca810b9e67e8e9b7252f6e7c a0f89796bbdce1aec071c8d6fb26894c6fa64ce5 Reavo End <decidel@gmail.com> 1782396886 -0700\tcommit: feat(cli): W3 — remove --reply-to + empty-scope create (v0.16.0)\n...\n 1257|e1e34d64e763cb9dbeb2190d5404f5ee67721ac0 4c1573b3fe2d4999c0b0c255e18aadc58d15a555 Reavo End <decidel@gmail.com> 1783416674 -0700\tcommit: feat(lifecycle-truth): W1 psyche echo unblock — drop-dir resolver on fire_echo, stamp-clear-any-success, optional-role skip\n*1258|4c1573b3fe2d4999c0b0c255e18aadc58d15a555 b6fa7c802942b6810690a3b0fcbcc853857ff245 Reavo End <decidel@gmail.com> 1783416776 -0700\tcommit: docs(triage): W5 INJECT-MULTILINE-INTEGRITY — 5th field instance (todlando W1 gate report head-truncated live SENT) + flynn empty-EVENT same night\n 1259|b6fa7c802942b6810690a3b0fcbcc853857ff245 f3c6b4c82ab55061cf4b82e2f2ce4de39afaa81a Reavo End <decidel@gmail.com> 1783417063 -0700\tcommit: docs(w2): decouple-shape design proposal for doyle ruling + triage W5 evidence (proposal part-1 eaten in transit — file transport adopted)\n 1260|f3c6b4c82ab55061cf4b82e2f2ce4de39afaa81a 60ec0ed0a42a4368a5c4a324765592bdc813d1f2 Reavo End <decidel@gmail.com> 1783417205 -0700\tcommit: docs(w2): doyle ruling — decouple APPROVED, minimal-plus scope, BRAIN_WRITE_DEADLINE 15s, passive resubscribe + observability rider\n 1261|60ec0ed0a42a4368a5c4a324765592bdc813d1f2 f1f275e368d1d478c575361b5466b18b5e144382 Reavo End <decidel@gmail.com> 1783417691 -0700\tcommit: docs(triage): split delivery defect into two roots (doyle 2026-07-07) — parked-idle late-flush (REQ-IDLE-PARKED-DELIVERY, clean wake trigger observed) vs live-SENT byte corruption (REQ-INJECT-MULTILINE-INTEGRITY)\n...\n 1463|3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n*1464|01d2d18cb3196313eb04e6a1fa03e28437fb5056 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tcheckout: moving from main to release/v0.34.0\n*1465|01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n*1466|eb9fd8643921930ce21cc7ef0751a27360fc14bc 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784211926 -0700\tcheckout: moving from release/v0.34.0 to main\n 1467|01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\n 1468|b7b074c2509c1015fe341185662b4622d661f274 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784236538 -0700\tcheckout: moving from main to fix/changelog-0330-heading\n 1469|b7b074c2509c1015fe341185662b4622d661f274 d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 Reavo End <decidel@gmail.com> 1784236564 -0700\tcommit: docs(changelog): restore consumed '## [0.33.0]' heading\n\n## refs/heads/\n### lifecycle-truth\n 4|e1e34d64e763cb9dbeb2190d5404f5ee67721ac0 4c1573b3fe2d4999c0b0c255e18aadc58d15a555 Reavo End <decidel@gmail.com> 1783416674 -0700\tcommit: feat(lifecycle-truth): W1 psyche echo unblock — drop-dir resolver on fire_echo, stamp-clear-any-success, optional-role skip\n*5|4c1573b3fe2d4999c0b0c255e18aadc58d15a555 b6fa7c802942b6810690a3b0fcbcc853857ff245 Reavo End <decidel@gmail.com> 1783416776 -0700\tcommit: docs(triage): W5 INJECT-MULTILINE-INTEGRITY — 5th field instance (todlando W1 gate report head-truncated live SENT) + flynn empty-EVENT same night\n 6|b6fa7c802942b6810690a3b0fcbcc853857ff245 f3c6b4c82ab55061cf4b82e2f2ce4de39afaa81a Reavo End <decidel@gmail.com> 1783417063 -0700\tcommit: docs(w2): decouple-shape design proposal for doyle ruling + triage W5 evidence (proposal part-1 eaten in transit — file transport adopted)\n 7|f3c6b4c82ab55061cf4b82e2f2ce4de39afaa81a 60ec0ed0a42a4368a5c4a324765592bdc813d1f2 Reavo End <decidel@gmail.com> 1783417205 -0700\tcommit: docs(w2): doyle ruling — decouple APPROVED, minimal-plus scope, BRAIN_WRITE_DEADLINE 15s, passive resubscribe + observability rider\n 8|60ec0ed0a42a4368a5c4a324765592bdc813d1f2 f1f275e368d1d478c575361b5466b18b5e144382 Reavo End <decidel@gmail.com> 1783417691 -0700\tcommit: docs(triage): split delivery defect into two roots (doyle 2026-07-07) — parked-idle late-flush (REQ-IDLE-PARKED-DELIVERY, clean wake trigger observed) vs live-SENT byte corruption (REQ-INJECT-MULTILINE-INTEGRITY)\n### v0.16.0-update-arc\n 4|42262e6d7d12dee8470df0f82f40ce8d10153ca3 159085754b1aeba397e32e89d4dd25b79fe0a7dd Reavo End <decidel@gmail.com> 1782393604 -0700\tcommit: feat(manifest): W1 — adapter-static subst keys + translation command (v0.16.0)\n*5|159085754b1aeba397e32e89d4dd25b79fe0a7dd fedc6ba58141b5e6a20a84e7c909ee15d4664ca5 Reavo End <decidel@gmail.com> 1782393746 -0700\tcommit: docs(v0.16.0): ratify translation command subst = adapter-static (W1 gate)\n 6|fedc6ba58141b5e6a20a84e7c909ee15d4664ca5 6db75f9d782845226c5b2070e930bd2e643d83d9 Reavo End <decidel@gmail.com> 1782395784 -0700\tcommit: feat(update): W2 — composite [update.post] delegated post-step (v0.16.0)\n 7|6db75f9d782845226c5b2070e930bd2e643d83d9 898401c1677c3749ca810b9e67e8e9b7252f6e7c Reavo End <decidel@gmail.com> 1782395914 -0700\tcommit: docs(v0.16.0): ratify W2 — self_verifies attestation-only + sentinel token\n 8|898401c1677c3749ca810b9e67e8e9b7252f6e7c a0f89796bbdce1aec071c8d6fb26894c6fa64ce5 Reavo End <decidel@gmail.com> 1782396886 -0700\tcommit: feat(cli): W3 — remove --reply-to + empty-scope create (v0.16.0)\n\n### release/\n#### v0.34.0\n 1|0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tbranch: Created from 01d2d18\n*2|01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"docs, .git/logs, CHANGELOG.md","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":14,"fileCount":9,"files":["docs/KNOWN-HAZARDS.md","docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md","docs/PUMP-W2-DESIGN.md","docs/PUMP-W2-DISPATCH-RULING.md","docs/RELEASE-RUNBOOK.md",".git/logs/HEAD",".git/logs/refs/heads/lifecycle-truth",".git/logs/refs/heads/release/v0.34.0",".git/logs/refs/heads/v0.16.0-update-arc"],"fileMatches":[{"path":"docs/KNOWN-HAZARDS.md","count":2},{"path":"docs/NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md","count":1},{"path":"docs/PUMP-W2-DESIGN.md","count":1},{"path":"docs/PUMP-W2-DISPATCH-RULING.md","count":1},{"path":"docs/RELEASE-RUNBOOK.md","count":1},{"path":".git/logs/HEAD","count":5},{"path":".git/logs/refs/heads/lifecycle-truth","count":1},{"path":".git/logs/refs/heads/release/v0.34.0","count":1},{"path":".git/logs/refs/heads/v0.16.0-update-arc","count":1}],"truncated":true,"displayContent":"# docs/\n## KNOWN-HAZARDS.md\n 667│- **Failure (paid-for, hertz field RCA 2026-07-16 — 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply — same `applyhost.rs` path) launches a fresh `dispatch::run_dispatch_loop` whose EMPTY per-process `claimed` set enumerates every broker-held peer stream. `NetShared.streams` has NO removal path (`StreamLog::finish` only marks) and the claim condition has no finished filter → the fresh dispatcher replays FINISHED historical attach streams. A repl...\n*668│- **Invariant:** redispatch eligibility is LIFECYCLE-gated: a finished/terminal stream row is retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach); classification identity is restart-durable and independent of the evictable data ring (immutable opener fact pinned per stream until close); claims are retryable on transient worker-setup failure and terminal on terminal outcomes (no hot-loop). The legitimate same-`by` successor re-take after a brain restart still s...\n 669│- **spt-core mapping:** `spt-daemon/src/dispatch.rs` `run_dispatch_loop` claim condition + `peek_first_line` classification + worker spawn; `spt-daemon/src/nethost.rs` `NetShared.streams` insert/finish + `DEFAULT_STREAM_RING_CHUNKS`; `spt-daemon/src/applyhost.rs` (the brain-cycle trigger).\n*670│- **Source:** REDISPATCH-TRUTH W1 (hertz source-level RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`, doyle seam-verified; ADR-0038; distinct from the CLOSED 0.30.x session-cursor resume-steal saga).\n 671│\n 672│---\n 673│\n## NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md\n 159│> (Field instances this milestone, all live-SENT, no spool copy, unrecoverable:\n*160│> todlando's W1 gate report HEAD-TRUNCATED at doyle; a flynn EVENT fully EMPTY.)\n 161│\n 162│Root: the idle-inject TYPED delivery leg eats HEAD bytes\n 163│nondeterministically — spool rows complete (1669B) vs ~322B received suffix; mid-turn poll\n## PUMP-W2-DESIGN.md\n 2│\n*3│`REQ-PUMP-PEER-ISOLATION`: concurrent non-blocking dials + per-peer fault isolation on the D4c presence-event seam. **Design-only** (no build; W1 gates first, box is deployah's for musl CI). For doyle's design-check before mint+build.\n 4│\n 5│## Premise check (CONTEXT / ADRs)\n 6│- **D4c presence seam is the sanctioned substrate.** `PresenceLog` (nethost.rs:418, ring `DEFAULT_STREAM_RING_CHUNKS`, cursor-resumable, gapless exactly-once, survives brain restart — nethost.rs:279) already carries `PRESENCE_CONNECTED` on every `register_conn` (nethost.rs:458-461). `NetPresenceEvent.kind` is a **string, explicitly so a newer broker can add kinds an older brain tolerates** (msg.rs:904) — adding a dial-outcome kind is the designed forward-compat path, not a wire break.\n## PUMP-W2-DISPATCH-RULING.md\n 2│\n*3│Rules the 5 forks in `docs/PUMP-W2-SCOPING.md` (todlando's sketch @e965bd9). **W2 = REQ-PUMP-PEER-ISOLATION, MANDATORY** (operator ruling 2026-07-08) — concurrent non-blocking dials + per-peer fault isolation. Activate `required_stages = [\"impl\",\"unit\",\"int\"]` at work-start. **W1 gates first** (fast stall-fix, still root-locked on the empirical >30s); W2 is the full wave after — NOT contingent on being the stall root ([[dont-solo-across-role-lines]]: doyle rules design, todlando builds, doyle gates, ...\n 4│\n 5│## Model — ENDORSE Option A (D4c-native)\n 6│The pump subscribes the existing `NetPresenceEvent` stream (cursor-resumable, gapless, exactly-once — nethost.rs:1289, brain.rs:825/1431), submits dials non-blocking, and runs each peer's `peer_step` when its conn-ready event arrives. **No new async substrate** — re-purpose D4c. Correct call; the broker is already async tokio+iroh and dials concurrently (nethost.rs:417).\n## RELEASE-RUNBOOK.md\n 52│     with no gap. Inserting the new heading with an editor whose anchor spans the\n*53│     previous heading can silently *consume* that heading (v0.34.0 did this to\n 54│     `## [0.33.0]`), folding the prior release's body into the new section — and\n 55│     since the body is published verbatim, the release notes then carry two\n 56│     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n\n# .git/logs/\n## HEAD\n  926│42262e6d7d12dee8470df0f82f40ce8d10153ca3 159085754b1aeba397e32e89d4dd25b79fe0a7dd Reavo End <decidel@gmail.com> 1782393604 -0700\tcommit: feat(manifest): W1 — adapter-static subst keys + translation command (v0.16.0)\n *927│159085754b1aeba397e32e89d4dd25b79fe0a7dd fedc6ba58141b5e6a20a84e7c909ee15d4664ca5 Reavo End <decidel@gmail.com> 1782393746 -0700\tcommit: docs(v0.16.0): ratify translation command subst = adapter-static (W1 gate)\n  928│fedc6ba58141b5e6a20a84e7c909ee15d4664ca5 6db75f9d782845226c5b2070e930bd2e643d83d9 Reavo End <decidel@gmail.com> 1782395784 -0700\tcommit: feat(update): W2 — composite [update.post] delegated post-step (v0.16.0)\n  929│6db75f9d782845226c5b2070e930bd2e643d83d9 898401c1677c3749ca810b9e67e8e9b7252f6e7c Reavo End <decidel@gmail.com> 1782395914 -0700\tcommit: docs(v0.16.0): ratify W2 — self_verifies attestation-only + sentinel token\n  930│898401c1677c3749ca810b9e67e8e9b7252f6e7c a0f89796bbdce1aec071c8d6fb26894c6fa64ce5 Reavo End <decidel@gmail.com> 1782396886 -0700\tcommit: feat(cli): W3 — remove --reply-to + empty-scope create (v0.16.0)\n     │...\n 1257│e1e34d64e763cb9dbeb2190d5404f5ee67721ac0 4c1573b3fe2d4999c0b0c255e18aadc58d15a555 Reavo End <decidel@gmail.com> 1783416674 -0700\tcommit: feat(lifecycle-truth): W1 psyche echo unblock — drop-dir resolver on fire_echo, stamp-clear-any-success, optional-role skip\n*1258│4c1573b3fe2d4999c0b0c255e18aadc58d15a555 b6fa7c802942b6810690a3b0fcbcc853857ff245 Reavo End <decidel@gmail.com> 1783416776 -0700\tcommit: docs(triage): W5 INJECT-MULTILINE-INTEGRITY — 5th field instance (todlando W1 gate report head-truncated live SENT) + flynn empty-EVENT same night\n 1259│b6fa7c802942b6810690a3b0fcbcc853857ff245 f3c6b4c82ab55061cf4b82e2f2ce4de39afaa81a Reavo End <decidel@gmail.com> 1783417063 -0700\tcommit: docs(w2): decouple-shape design proposal for doyle ruling + triage W5 evidence (proposal part-1 eaten in transit — file transport adopted)\n 1260│f3c6b4c82ab55061cf4b82e2f2ce4de39afaa81a 60ec0ed0a42a4368a5c4a324765592bdc813d1f2 Reavo End <decidel@gmail.com> 1783417205 -0700\tcommit: docs(w2): doyle ruling — decouple APPROVED, minimal-plus scope, BRAIN_WRITE_DEADLINE 15s, passive resubscribe + observability rider\n 1261│60ec0ed0a42a4368a5c4a324765592bdc813d1f2 f1f275e368d1d478c575361b5466b18b5e144382 Reavo End <decidel@gmail.com> 1783417691 -0700\tcommit: docs(triage): split delivery defect into two roots (doyle 2026-07-07) — parked-idle late-flush (REQ-IDLE-PARKED-DELIVERY, clean wake trigger observed) vs live-SENT byte corruption (REQ-INJECT-MULTILINE-INTEGRITY)\n     │...\n 1463│3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n*1464│01d2d18cb3196313eb04e6a1fa03e28437fb5056 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tcheckout: moving from main to release/v0.34.0\n*1465│01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n*1466│eb9fd8643921930ce21cc7ef0751a27360fc14bc 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784211926 -0700\tcheckout: moving from release/v0.34.0 to main\n 1467│01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\n 1468│b7b074c2509c1015fe341185662b4622d661f274 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784236538 -0700\tcheckout: moving from main to fix/changelog-0330-heading\n 1469│b7b074c2509c1015fe341185662b4622d661f274 d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 Reavo End <decidel@gmail.com> 1784236564 -0700\tcommit: docs(changelog): restore consumed '## [0.33.0]' heading\n\n## refs/heads/\n### lifecycle-truth\n 4│e1e34d64e763cb9dbeb2190d5404f5ee67721ac0 4c1573b3fe2d4999c0b0c255e18aadc58d15a555 Reavo End <decidel@gmail.com> 1783416674 -0700\tcommit: feat(lifecycle-truth): W1 psyche echo unblock — drop-dir resolver on fire_echo, stamp-clear-any-success, optional-role skip\n*5│4c1573b3fe2d4999c0b0c255e18aadc58d15a555 b6fa7c802942b6810690a3b0fcbcc853857ff245 Reavo End <decidel@gmail.com> 1783416776 -0700\tcommit: docs(triage): W5 INJECT-MULTILINE-INTEGRITY — 5th field instance (todlando W1 gate report head-truncated live SENT) + flynn empty-EVENT same night\n 6│b6fa7c802942b6810690a3b0fcbcc853857ff245 f3c6b4c82ab55061cf4b82e2f2ce4de39afaa81a Reavo End <decidel@gmail.com> 1783417063 -0700\tcommit: docs(w2): decouple-shape design proposal for doyle ruling + triage W5 evidence (proposal part-1 eaten in transit — file transport adopted)\n 7│f3c6b4c82ab55061cf4b82e2f2ce4de39afaa81a 60ec0ed0a42a4368a5c4a324765592bdc813d1f2 Reavo End <decidel@gmail.com> 1783417205 -0700\tcommit: docs(w2): doyle ruling — decouple APPROVED, minimal-plus scope, BRAIN_WRITE_DEADLINE 15s, passive resubscribe + observability rider\n 8│60ec0ed0a42a4368a5c4a324765592bdc813d1f2 f1f275e368d1d478c575361b5466b18b5e144382 Reavo End <decidel@gmail.com> 1783417691 -0700\tcommit: docs(triage): split delivery defect into two roots (doyle 2026-07-07) — parked-idle late-flush (REQ-IDLE-PARKED-DELIVERY, clean wake trigger observed) vs live-SENT byte corruption (REQ-INJECT-MULTILINE-INTEGRITY)\n### v0.16.0-update-arc\n 4│42262e6d7d12dee8470df0f82f40ce8d10153ca3 159085754b1aeba397e32e89d4dd25b79fe0a7dd Reavo End <decidel@gmail.com> 1782393604 -0700\tcommit: feat(manifest): W1 — adapter-static subst keys + translation command (v0.16.0)\n*5│159085754b1aeba397e32e89d4dd25b79fe0a7dd fedc6ba58141b5e6a20a84e7c909ee15d4664ca5 Reavo End <decidel@gmail.com> 1782393746 -0700\tcommit: docs(v0.16.0): ratify translation command subst = adapter-static (W1 gate)\n 6│fedc6ba58141b5e6a20a84e7c909ee15d4664ca5 6db75f9d782845226c5b2070e930bd2e643d83d9 Reavo End <decidel@gmail.com> 1782395784 -0700\tcommit: feat(update): W2 — composite [update.post] delegated post-step (v0.16.0)\n 7│6db75f9d782845226c5b2070e930bd2e643d83d9 898401c1677c3749ca810b9e67e8e9b7252f6e7c Reavo End <decidel@gmail.com> 1782395914 -0700\tcommit: docs(v0.16.0): ratify W2 — self_verifies attestation-only + sentinel token\n 8│898401c1677c3749ca810b9e67e8e9b7252f6e7c a0f89796bbdce1aec071c8d6fb26894c6fa64ce5 Reavo End <decidel@gmail.com> 1782396886 -0700\tcommit: feat(cli): W3 — remove --reply-to + empty-scope create (v0.16.0)\n\n### release/\n#### v0.34.0\n 1│0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tbranch: Created from 01d2d18\n*2│01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784247488042}}
{"type":"message","id":"35cf06a2","parentId":"e537c6d6","timestamp":"2026-07-17T00:18:08.072Z","message":{"role":"toolResult","toolCallId":"call_R7ish7YBHKF9DiT12TpOXSz7|fc_099a7567dadef391016a5974bfba74819b970f1be1999a61bf","toolName":"grep","content":[{"type":"text","text":"# traceable-reqs.toml\n 413|title = \"No endpoint process terminates/suspends during self-update\"\n*414|required_stages = [\"impl\", \"unit\", \"int\"]  # int RE-POINTED at restoration D7-1 (ADR-0018 V5) to the PROCESS-level survival E2E (crates/spt/tests/brain_survive.rs: no endpoint terminates across a brain-only update at the PROCESS level — a real brain-child respawn onto a swapped on-disk binary, the PTY child's pid unchanged + held-AND-functional + the QUIC conn intact). Was M3c-C0 brain_swap.rs (the in-process Brain::handoff closure shape — regression-masked per ADR-0018); that tag removed in the same...\n 415|[[requirements]]\n 416|id = \"REQ-UPD-4\"\n 417|title = \"Update gated on user confirmation by default; opt-in full-auto\"\n...\n 495|id = \"REQ-HOST-RUN-2\"\n*496|title = \"Project-scoped working directory for spt-hosted bringup: `spt endpoint run` lands the broker-spawned harness PTY in the user's PROJECT cwd, not the daemon's, via an additive `SpawnReq.cwd` field carried through the broker PTY spawn (portable-pty CommandBuilder cwd). N-1-safe wire change (additive, defaulted). Required because the consumer (Claude Code) is project-scoped: broker-inherited cwd = the daemon's cwd = the wrong `.claude`, wrong session history, wrong digest source; `cc <id>` at a proj...\n 497|required_stages = [\"impl\", \"unit\", \"int\"]  # activated M12-W1.5: impl = additive SpawnReq.cwd + PtySession::spawn_program_in (portable-pty CommandBuilder cwd) + broker dispatch_spawn honors it + harnesshost/cli thread project cwd (std::env::current_dir); unit = spawn_program_in_lands_the_child_in_the_requested_cwd (spt-term); int = broker_spawns_the_pty_child_in_the_requested_cwd (wire field → broker spawn → PTY cwd, end to end)\n 498|# ── M12 Wave 2: interactive `spt endpoint run` picker (M12-PLAN.md Wave 2 / M12-ENDPOINT-RUN-PICKER.md;\n 499|#    doyle ruling M12-W2-RULING.md). Pure front-end over existing surfaces — no second bringup path\n...\n 954|id = \"REQ-HAZARD-BROKER-SEED-WIRE-SKEW\"\n*955|title = \"A daemon-state wire-format change (e.g. the v0.9.0 adapter-agnostic Seed) does NOT take effect until a DELIBERATE full broker restart: the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004 no-terminate-during-update forbids auto-killing it), so a NEW-version CLI talking to a still-resident OLD broker fails the seed handshake — the old broker cannot deserialize the new Seed (its formerly-required `adapter` field is gone) and drops the conn without ...\n 956|required_stages = [\"doc\", \"impl\", \"unit\"]  # activated v0.9.1: doc = KNOWN-HAZARDS entry. impl = cmd_seed appends the actionable stale-broker hint when put_seed's seed-ack read returns UnexpectedEof (scoped to that path, not unrelated EOFs). unit = a seed-control server that reads the PUT frame then drops the conn without acking (the old-broker deser-fail close) → cmd_seed surfaces the `spt daemon stop` hint, not a bare buffer error\n 957|[[requirements]]\n 958|id = \"REQ-HAZARD-SUDO-SECURE-PATH\"\n...\n 987|id = \"REQ-HAZARD-BROKER-PROCESS-ISOLATION\"\n*988|title = \"Broker and brain are separate processes: the broker runs as its own long-lived per-machine process that survives every brain restart, so a routine (brain-only) self-update restarts the brain onto the swapped binary while every hosted endpoint (PTY child, live QUIC conn, listening socket) stays untouched at the PROCESS level. The in-process-thread broker (daemon.rs:165-170) is a regression that silently unrealizes REQ-UPD-3 — apply degrades to an in-process Brain::handoff no-op and new code doe...\n 989|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # activated restoration D1 (ADR-0018 Q2/Q3): doc = KNOWN-HAZARDS 6.7 + ADR-0018 + design doc; impl = brainproc.rs (brain child entry + broker-side process supervisor) + daemon.rs spawn-brain-supervisor + the hidden `daemon brain` CLI entry; unit = brainproc supervisor respawn/backoff table + the real-binary brain_split.rs process-level smoke (broker survives a brain kill + respawns it). int ACTIVATED restoration D7 = the productionized SPIKE-01/03 process...\n 990|[[requirements]]\n 991|id = \"REQ-HAZARD-ROLLBACK-STATE-COMPAT\"\n...\n 1217|id = \"REQ-HAZARD-ATTACH-WEDGE\"\n*1218|title = \"A legitimately dead PTY child (real crash/kill) + an undrained operator pump must NOT wedge the broker for all other clients. ROOT (v0.12.0 real-harness defect): loopback attach output is a blocking write_all into a bounded 64KB tokio duplex (nethost.rs:1040,1090); when the operator's rc pump stops draining (tab closed) the buffer fills and write_all blocks forever (the 'loopback never hangs' assumption at nethost.rs:1103 is false), parking a worker in the 2-worker net runtime (nethost.rs:640); ...\n 1219|required_stages = [\"int\"]  # ACTIVATED v0.12.1 L2 (todlando) as PROVE-DON'T-CHANGE (like the M11-W4 capstone): the post-L0 code ALREADY prevents the wedge, so a test proves it and NO impl/unit code changes. The original \"loopback write_all blocks forever → parks the 2-worker net runtime\" root is STALE: (1) serve_attach forwards via fire-and-forget net_stream_send(op_id=None) → broker-side send_stream is ALREADY bounded by bounded_block_on (BROKER-QUIC-DEADLINE, 10s), not forever; (2) the loopback dup...\n 1220|[[requirements]]\n 1221|id = \"REQ-PICKER-HISTORY-FRESH\"\n...\n 1407|id = \"REQ-HAZARD-CONTROLLER-WRITER-REORDER\"\n*1408|title = \"Two `controller_writer` threads must never race ONE brain↔broker connection's socket. ROOT (doyle, instrumented RACEDIAG repro on kitsubito): on a brain-restart re-serve the handoff brain registers as controller on the SAME session TWICE over the SAME `Brain::conn` socket — (1) `Brain::handoff` eagerly `subscribe(prior.session_id, prior.next_seq=1)` → `become_controller(from_seq=1)`, initial=[1], spawns writer-A (writes seq 1); (2) `serve_attach` re-handles the replayed `Request{from_seq:0...\n 1409|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED v0.13.0 P1c (todlando, the last v0.13.0 ship-blocker — pre-existing flaky resume race that gates green-both-runners; doyle root-caused + designed, doyle gates). doc = KNOWN-HAZARDS entry (the reorder + the single-live-writer-per-connection invariant + the two compounding defects) + CONTEXT note. impl = (1) Brain::handoff KEEPS its eager subscribe(prior.session_id, prior.next_seq) [fix #1 REVERTED — load-bearing resume] AND seeds session_cu...\n 1410|\n 1411|# ── P1c follow-up SEED (doyle proposed, todlando gated, doyle concurred 2026-06-20): the structural per-connection-floor guard — inactive, activate-don't-pre-fail ──\n...\n 1558|id = \"REQ-UPDATE-RUNNING-IMAGE-SURFACE\"\n*1559|title = \"`spt` surfaces the RUNNING broker image version beside the on-disk version so an updated-looking node reveals broker-side dormancy. ROOT (F-025): `spt update apply` restarts the BRAIN only (ADR-0018 D3-3) — the BROKER process survives and keeps running its pre-apply compiled image, so every broker-side surface of a freshly-applied release (the F015B live-apply matcher, dispatch inject legs, etc.) is silently dormant until a full daemon bounce, with nothing in the CLI revealing the split. FIX: ...\n*1560|required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED F-025 item 1a + final-wave int (todlando). impl = KIND_BROKER_IMAGE/_REPLY + BrokerImageReply (msg.rs) → broker self-report dispatch_broker_image from env!(\"CARGO_PKG_VERSION\") (broker.rs) + Brain::broker_image_version client reading the reply, KIND_ERROR (old broker's `unknown command kind`) → None (brain.rs) + cmd_daemon_status queries both net-status and the image on ONE handle and renders render_broker_image_line beside the installed version;...\n 1561|[[requirements]]\n 1562|id = \"REQ-UPDATE-APPLY-RESTART-NOTICE\"\n 1563|title = \"`spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) — name the user-visible CONSEQUENCE ('daemon-coordinated features run the previous version until the daemon restarts'), not the broker/brain internals. Comp...\n*1564|required_stages = [\"impl\", \"unit\"]  # ACTIVATED F-025 item 1b (todlando). impl = render_applied_message appends restart_required_notice() (cli.rs) on every successful apply — names the consequence (daemon-coordinated features run the previous version), the restart recovery (`spt daemon stop`/`start`), and points at the `spt daemon status` surface (1a); public wording, no broker/brain internals. unit = applied_message_appends_restart_required_notice (keeps the confident head + names consequence + restar...\n 1565|[[requirements]]\n 1566|id = \"REQ-UPDATE-APPLY-ALREADY-APPLIED\"\n 1567|title = \"`spt update apply` classifies an already-staged / already-applied state as a friendly exit-0 no-op instead of dying at the binary-aside rename with 'Access is denied (os error 5)'. ROOT (F-025): a second apply on an already-applied staged version reaches the two-phase binary-aside rename and fails os-error-5, reading as a hard failure when the machine is simply up to date. FIX: apply gains the same pure classifier `fetch` got in v0.18.0 (REQ-UPDATE-FETCH-CURRENT-UX) — already-applied → clear...\n...\n 1980|id = \"REQ-HAZARD-BROKER-FLOOR-LOCK-POISON\"\n*1981|title = \"B-1 (REMOTE-TRUTH triage §B-1, PIVOTED @e5eb99a): NO bare `.lock().unwrap()` on a broker-resident lock reachable from serve/dispatch — a brain-only self-update keeps the broker + all its Mutexes ALIVE (REQ-UPD-3), so a single panic under one poisons it PERMANENTLY: the next `.lock().unwrap()` panics, kills its per-conn reply thread, and EVERY subsequent attach silently deadlines ('brain IPC read deadline elapsed') while non-locked ops keep working. TRIAGE-DRIFT (sweep-dispatch-site-counts dis...\n 1982|required_stages = [\"doc\", \"impl\", \"unit\"]  # ACTIVATED REMOTE-TRUTH B-1 PIVOTED (todlando). doc = KNOWN-HAZARDS.md 7.33 (class-level: NO bare .lock().unwrap() on a serve/dispatch-reachable broker-resident lock — recover/recover_log or documented fail-fast). impl = broker::recover<T> (into_inner shared helper) applied at self.sessions ×18 + sessions_exit + pair_holds ×4 (23), broker::recover_log (recover + coherence clamp on the poison branch) at the 11 OutputLog ring lock sites, OutputLog::clamp_or_r...\n 1983|[[requirements]]\n 1984|id = \"REQ-CONTROLLER-LIVENESS-REAP\"\n*1985|title = \"B-2 (REMOTE-TRUTH triage §B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals — the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a tr...\n 1986|required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REMOTE-TRUTH B-2 REDUCED (todlando); int ACTIVATED on the green [twohost] rig run 28772170556 @279df99 (2026-07-06). impl = OutputLog::reap_dead_controller (broker.rs: drop a controller whose _writer.is_finished() in-memory ONLY, no stamp — lock-safe under the KIND_SESSIONS snapshot log lock) + the snapshot-closure call site (recover_log(&log) made mut; reap BEFORE reading controller_by/has_controller so the reply + the existing off-lock converge_p...\n 1987|[[requirements]]\n 1988|id = \"REQ-ADAPTER-FLOOR-ENFORCE\"\n...\n 2386|id = \"REQ-UPDATE-DEFAULT-COMPOSITE\"\n*2387|title = \"THE-FORKENING W4 (operator-grilled 2026-07-14): plain `spt update` = `update fetch --apply` THEN `update adapters` (core-first doctrine order); when core is already current the core leg no-ops and ONLY adapters update; `--core-only`/`-c` skips the adapters leg. GROUNDING (operator-corrected, code-confirmed): fetch --apply cycles the BRAIN only — broker + PTYs survive (apply_staged applyhost.rs:303; the restart-required text is a NOTICE, cli.rs:4615, not behavior) — so the composite's invokin...\n 2388|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2389|\n 2390|[[requirements]]\n\n# CONTEXT.md\n 582|\n*583|**daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previously required a full daemon bounce. <!-- [doc->REQ-DAEMON-REFRESH] -->\n 584|\n 585|**project index** — a node's endpoint→project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers — list, picker, endpoint-info, hoo...\n 586|\n\n# crates/\n\n## spt-daemon/src/\n### applyhost.rs\n 20|//! 2. **Classify** against the running broker's ABI. Only\n*21|//!    [`UpdateClass::BrainOnly`] applies in M5 — the broker-touching classes\n 22|//!    refuse typed, the artifact stays staged (their consent-and-scheduling\n 23|//!    orchestration is post-M5; ADR-0004 §A).\n 24|//! 3. **Swap the binary**: the current executable steps aside\n...\n 49|use crate::release::{current_platform, VerifyPolicy};\n*50|use crate::update::{plan_verified, plan_verified_update_set, BrokerAbi, UpdateClass};\n 51|\n 52|/// What [`apply_staged`] did — loud + typed, never silent.\n 53|#[derive(Debug, Clone, PartialEq, Eq)]\n...\n 68|    Rejected(String),\n*69|    /// Verified but not brain-only — the M5 apply surface refuses the\n 70|    /// broker-touching classes typed; the artifact stays staged.\n*71|    RefusedClass(UpdateClass),\n 72|    /// Refused: this version was auto-rolled-back on this node (a prior trial\n 73|    /// failed readiness) and is **quarantined** — never re-applied until a newer\n 74|    /// version stages or an operator forces it (the deferred escape hatch). The\n...\n 111|    Done(ApplyStagedOutcome),\n*112|    /// Verified brain-only bytes ready to land at the exe path.\n 113|    Proceed {\n 114|        version: u64,\n 115|        product_version: String,\n...\n 121|/// already-applied short-circuit, at-rest re-verification, platform guard, and\n*122|/// the brain-only class gate. Pure w.r.t. the daemon — reads only the cache +\n 123|/// keys, swaps nothing. Both the broker-handoff apply and the daemonless apply\n 124|/// run this identical gate so their fail-closed posture never diverges.\n 125|// [impl->REQ-UPD-4]\n...\n 236|    };\n*237|    if plan.class != UpdateClass::BrainOnly {\n 238|        return Ok(Prepared::Done(ApplyStagedOutcome::RefusedClass(plan.class)));\n 239|    }\n 240|\n...\n 332|    // regression: it re-attached a CLI-side subscriber while the live brain kept\n*333|    // running the OLD code). `classify` already gated this as brain-only, so the\n 334|    // broker never moves; only the brain restarts.\n 335|    //\n 336|    // A broker without a supervisor wired (`honored: false`) still swapped the\n...\n 577|            .broker_image_version()\n*578|            .expect(\"broker image query round-trips\");\n 579|        assert_eq!(\n 580|            v.as_deref(),\n 581|            Some(env!(\"CARGO_PKG_VERSION\")),\n...\n 872|    // [unit->REQ-UPD-4] the ack-driven apply end to end: a staged + verified\n*873|    // brain-only release swaps the binary (artifact lands at the exe path,\n 874|    // the original steps aside) and triggers the brain handoff with the\n 875|    // hosted endpoint untouched — the production caller the M4-D9 residual\n 876|    // promised. [unit->REQ-UPD-3]\n...\n 980|                out,\n*981|                ApplyStagedOutcome::RefusedClass(UpdateClass::BrokerBreaking)\n 982|            ),\n 983|            \"got {out:?}\"\n 984|        );\n...\n 1033|    // [unit->REQ-UPD-3] A node with no broker-hosted sessions still applies a\n*1034|    // brain-only update: there is nothing to hand off, and no endpoint can be\n 1035|    // terminated by the apply.\n 1036|    #[test]\n 1037|    fn apply_staged_without_broker_hosted_sessions_swaps_binary() {\n...\n 1077|                out,\n*1078|                ApplyStagedOutcome::RefusedClass(UpdateClass::BrokerBreaking)\n 1079|            ),\n 1080|            \"got {out:?}\"\n 1081|        );\n...\n 1086|    // [unit->REQ-UPD-4] idempotent no-op and fail-closed gates: nothing\n*1087|    // staged ⇒ NoUpdate; a non-brain-only class refuses typed with the seat\n 1088|    // untouched; a tampered at-rest artifact rejects with the seat untouched.\n 1089|    #[test]\n 1090|    fn apply_staged_gates_fail_closed_with_the_seat_untouched() {\n...\n 1108|                out,\n*1109|                ApplyStagedOutcome::RefusedClass(UpdateClass::BrokerBreaking)\n 1110|            ),\n 1111|            \"got {out:?}\"\n 1112|        );\n### brain.rs\n 369|    /// The eager `subscribe` IS the resume mechanism — every standalone handoff\n*370|    /// caller (the brain-only update engine, `idempotent`/`daemon_e2e`/`handoff`\n 371|    /// integration tests) replays output through it with no later `attach`. So it\n 372|    /// stays. The P1c controller-writer reorder (a `serve_attach` re-serve that\n 373|    /// ALSO subscribes from 0 over this same socket → two racing\n### broker.rs\n 1829|/// Poison-tolerant lock for ANY broker-resident mutex reachable from serve/dispatch\n*1830|/// (B-1, REQ-HAZARD-BROKER-FLOOR-LOCK-POISON). A brain-only self-update keeps the\n 1831|/// broker process — and every one of its `Mutex`es — ALIVE (REQ-UPD-3), so a single\n 1832|/// panic while another thread held one of these locks would otherwise POISON it\n 1833|/// permanently: the next `.lock().unwrap()` panics too, its per-conn reply thread\n...\n 3854|    /// version at build time, so a broker that SURVIVED a `spt update apply`\n*3855|    /// (brain-only restart, ADR-0018 D3-3) honestly reports the PRE-apply version\n 3856|    /// — the authoritative running-image truth the `daemon status` version surface\n 3857|    /// compares against the freshly-installed CLI. Sourced from the running\n 3858|    /// process, never inferred from disk (the disk is the half already ahead).\n...\n 5143|    // (the SESSIONS class — sessions/pair_holds) RECOVERS from a poison instead of\n*5144|    // panicking. Brain-only self-update keeps the broker + this lock alive; a panic\n 5145|    // under it during attach churn would poison it permanently → EVERY subsequent\n 5146|    // attach (serve/dispatch locks sessions) panics its reply thread and deadlines\n 5147|    // (\"brain IPC read deadline elapsed\") forever. `recover` (the fix) hands back a\n### lib.rs\n 43|//! **C0 (this commit)** opens M3c — the self-update engine ([`update`]): the\n*44|//! ADR-0004 §A update-class taxonomy ([`UpdateClass`] + [`classify`]) and the\n*45|//! **brain-only zero-interruption swap** ([`apply_brain_only`], [`REQ-UPD-3`]).\n*46|//! The brain-only apply *triggers* a handoff over the B2 substrate — snapshot →\n 47|//! drop → re-attach — so the live PTY child + its output stream survive the logic\n 48|//! swap untouched; it never re-implements the handoff and never touches a broker\n 49|//! resource.\n...\n 263|    apply_brain_only, classify, plan_update, plan_verified, plan_verified_update_set, ApplyError,\n*264|    BrokerAbi, ReleaseSpec, UpdateClass, UpdatePlan, BROKER_RESOURCE_ABI,\n 265|};\n 266|pub use wan::{receive_wan, request_wan, WanOutcome, WanRequestOutcome};\n 267|pub use xfer::{fetch_file, push_file, serve_xfer, XferServeOutcome};\n### msg.rs\n 99|pub const KIND_BROKER_IMAGE: &str = \"broker-image\";\n*100|/// Broker→brain: the broker image version reply.\n 101|pub const KIND_BROKER_IMAGE_REPLY: &str = \"broker-image-reply\";\n 102|/// Brain→broker query: the brain-subscriber stall-evict tally (LIFECYCLE-TRUTH W2\n 103|/// observability rider, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) — how many\n### notif.rs\n 57|/// Producer kind for the auto-rollback alert (ADR-0018 Q7, restoration D6-2): a\n*58|/// brain-only update failed readiness and the supervisor rolled back to the\n 59|/// last-known-good binary. A distinct kind so a surface can style it; it rides\n 60|/// the same first-fire + **boundary-resurface** path as every other producer, so\n 61|/// an undismissed rollback row re-surfaces until the operator dismisses it\n...\n 425|/// Produce + first-fire the loud auto-rollback alert (restoration D6-2, ADR-0018\n*426|/// Q7): a brain-only update `v{quarantine_version}` failed to reach `ready` and\n 427|/// the supervisor rolled back to `v{running_version}`, quarantining the bad\n 428|/// version. Reuses the one producer front door ([`produce_and_first_fire`]) so\n 429|/// the row rides first-fire AND **boundary-resurface** — an undismissed rollback\n### update.rs\n*1|//! The self-update engine (C0) — the update-class taxonomy and the brain-only\n 2|//! zero-interruption swap.\n 3|//!\n 4|//! ADR-0004 §A splits a self-update into **three classes**, each with its own\n...\n 6|//!\n*7|//! - **brain-only** — the routine case. The running broker can already host the\n 8|//!   release's brain (the versioned-IPC handshake accepts it) and the broker's\n 9|//!   held-resource ABI is unchanged, so only the logic swaps. **No endpoint\n 10|//!   process terminates or suspends** ([`REQ-UPD-3`], absolute for this class) —\n...\n 20|//!\n*21|//! C0 implements the **classifier** ([`classify`]) and the **brain-only apply**\n 22|//! ([`apply_brain_only`]). The apply *triggers a handoff* over the existing M3b\n 23|//! substrate ([`crate::brain::Brain::handoff`]) — it does **not** re-implement\n 24|//! one (clean-room posture, `M3c-PLAN.md` §C0). The signing/rollback hardening\n...\n 40|/// an existing handle is carried. A pure logic change (the routine update) never\n*41|/// touches this — that is precisely what keeps the routine case *brain-only*.\n 42|pub const BROKER_RESOURCE_ABI: u32 = 1;\n 43|\n 44|/// What the currently-running broker provides — the fixed point an incoming\n...\n 89|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n*90|pub enum UpdateClass {\n 91|    /// Swap logic only; the running broker hosts the new brain unchanged. No\n 92|    /// endpoint terminates ([`REQ-UPD-3`], absolute). The routine case.\n*93|    BrainOnly,\n 94|    /// The broker binary hot-swaps behind the versioned IPC (held-resource type\n 95|    /// unchanged); endpoints survive. Apply path deferred (no spike — ADR-0004 §A).\n 96|    BrokerCompatible,\n...\n 110|///    is at or above the running broker's floor, the handshake accepts it and\n*111|///    only logic swaps: **brain-only**. Otherwise the broker must move to a build\n 112|///    that speaks the new IPC — but since the resource type is unchanged the new\n 113|///    broker adopts the existing handles: **broker-compatible**.\n 114|// [impl->REQ-UPD-3]\n*115|pub fn classify(running: &BrokerAbi, release: &ReleaseSpec) -> UpdateClass {\n 116|    if release.broker_resource_abi != running.resource_abi {\n*117|        return UpdateClass::BrokerBreaking;\n 118|    }\n 119|    if release.brain_ipc_version >= running.min_compatible {\n*120|        UpdateClass::BrainOnly\n 121|    } else {\n*122|        UpdateClass::BrokerCompatible\n 123|    }\n 124|}\n 125|\n...\n 131|    /// The update class (ADR-0004 §A).\n*132|    pub class: UpdateClass,\n 133|    /// The release the plan was built for.\n 134|    pub release: ReleaseSpec,\n 135|}\n...\n 139|    UpdatePlan {\n*140|        class: classify(running, &release),\n 141|        release,\n 142|    }\n 143|}\n...\n 184|\n*185|/// Why a brain-only apply did not complete.\n 186|#[derive(Debug)]\n 187|pub enum ApplyError {\n*188|    /// The plan is not brain-only — routing it through the brain-only path would\n 189|    /// risk an endpoint, which this class must never do. Raised **before** the\n 190|    /// outgoing brain is touched. The caller routes a broker-compatible /\n 191|    /// broker-breaking plan through its own (consent-gated, scheduled) path;\n*192|    /// brain-only is the only swap C0 performs.\n*193|    NotBrainOnly(UpdateClass),\n 194|    /// The new brain failed to re-attach (the `relaunch` closure errored). The\n 195|    /// endpoint is **untouched** regardless — the broker + its hosted child\n 196|    /// persist across a failed re-attach exactly as across a brain crash — so the\n...\n 203|        match self {\n*204|            ApplyError::NotBrainOnly(c) => {\n 205|                write!(\n 206|                    f,\n 207|                    \"apply_brain_only refused a {c:?} plan (would risk an endpoint)\"\n...\n 210|            ApplyError::Relaunch(e) => {\n*211|                write!(f, \"brain-only relaunch failed (endpoint intact): {e}\")\n 212|            }\n 213|        }\n 214|    }\n...\n 219|        match self {\n*220|            ApplyError::NotBrainOnly(_) => None,\n 221|            ApplyError::Relaunch(e) => Some(e),\n 222|        }\n 223|    }\n\n## spt/src/\n### cli.rs\n 4160|    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n*4161|    broker_stale: Option<bool>,\n 4162|    /// LIFECYCLE-TRUTH W2 observability rider: brain-subscriber stall-evicts since\n 4163|    /// broker boot (a controller detached for a suspended/black-holed brain conn).\n 4164|    /// `None` when the daemon is down or the broker is too old to report; `Some(0)`\n...\n 4213|    match running {\n*4214|        Some(v) if v == installed => format!(\"broker image: {v} (matches installed)\"),\n 4215|        Some(v) => format!(\n*4216|            \"broker image: {v} (running) — installed is {installed}; the daemon is still on the \\\n 4217|             previous version, so daemon-coordinated features run {v} until it restarts. Restart \\\n 4218|             with `spt daemon stop` then `spt daemon start`.\"\n 4219|        ),\n 4220|        None => format!(\n*4221|            \"broker image: not reported (the running daemon predates version reporting, or could \\\n 4222|             not be queried) — if it is older than the installed {installed}, restart with \\\n 4223|             `spt daemon stop` then `spt daemon start` to load {installed}.\"\n 4224|        ),\n...\n 4388|        // queried (no false-positive true on a transient failure — doyle F-025 nit).\n*4389|        let broker_stale = (running && broker_queried)\n 4390|            .then(|| broker_image.as_deref() != Some(env!(\"CARGO_PKG_VERSION\")));\n 4391|        let pump_heartbeat_ms = if running {\n 4392|            spt_daemon::read_pump_heartbeat()\n...\n 4451|            broker_image,\n*4452|            broker_stale,\n 4453|            stall_evict_count,\n 4454|            stall_evict_last_ms,\n 4455|            project_index: spt_daemon::projwriter::read_stats_at(\n...\n 5047|    /// `update fetch --apply` — stage + install the core in one step (the\n*5048|    /// brain-only cycle: the invoking process survives by construction).\n 5049|    FetchApply,\n 5050|    /// `update fetch` — stage only; the swap waits for the Finish leg.\n 5051|    Fetch,\n\n### api/\n#### startup.rs\n 69|/// (`UnexpectedEof` = \"failed to fill whole buffer\") is the daemon-state\n*70|/// wire-skew footgun: a still-resident OLD broker (resident across a brain-only\n 71|/// self-update — ADR-0004) cannot deserialize the new Seed and drops the conn\n 72|/// without acking. That case gets the actionable cause + fix; any other error\n 73|/// kind keeps the raw message (so the hint never mis-fires on a refused connect\n\n# docs/\n## BROKER-BRAIN-SPLIT-RESTORATION.md\n 84|\n*85|Restore the two-process model so that a routine **brain-only** update (the common case per ADR-0004 §A's class taxonomy) hands off to the new executable seamlessly:\n 86|\n 87|1. no endpoint or harness child process is dropped or suspended (REQ-UPD-3),\n 88|2. internal loop timings stay consistent across the swap,\n...\n 91|\n*92|The broker-touching update classes (broker-compatible / broker-breaking) remain as ADR-0004 left them — out of scope here; this work is the **brain-only** path's realization.\n 93|\n 94|---\n 95|\n...\n 182|\n*183|**Decision/observation.** The broker spawns the brain as a **child process** (`Command::spawn`) and they communicate over the existing local socket IPC. **No `exec` is required**, so there is no Windows/Unix divergence. ConPTY (Windows) and forkpty (Linux) handles stay broker-side and never cross to the brain. Spikes 01 and 04 already proved the handoff on both OSes.\n 184|\n 185|**Reason.** This is precisely the uniformity ADR-0004:25 sought when it rejected whole-daemon live FD-passing as \"hard and platform-divergent.\" Spawn-plus-socket sidesteps it entirely.\n 186|\n...\n 225|- **ADR-0004** — `docs/adr/0004-single-daemon-broker-brain-split-and-self-update.md` (the decision being restored; §A update classes, §B ownership table, the spike amendments).\n*226|- **Requirements** — REQ-UPD-3 (no-endpoint-drop, brain-only), REQ-UPD-4 (staged + consent), REQ-UPD-6/8 (update-set / platform-safe), REQ-DAEMON-3/9 (auto-start / net self-heal), REQ-HAZARD-UPDATE-ROLLBACK, REQ-HAZARD-HANDOFF-ARGV-COMPAT.\n 227|- **Glossary/model** — `CONTEXT.md` daemon / broker / brain (incl. the one-broker-per-machine clause added this session).\n 228|- **Spikes** — `docs/spikes/SPIKE-01-broker-handoff.md`, `-03-quic-survival`, `-04-forkpty-parity`, `-05-restart-stress`, `-06-idempotent-boundary` (proofs that the two-process handoff works on both OSes).\n 229|- **Key source** — `daemon.rs` (`run`, net consumers, self-heal, digest hub, shellwake, seed-control), `broker.rs` (`OutputLog`, `bind`, `EffectJournal`), `brain.rs` (`BrainState`, `cold_start`/`handoff`/`snapshot`), `applyhost.rs` (`apply_staged`, `connect_retry`, record-before-handoff), `update.rs` (classes, `classify`, `apply_brain_only`), `peerloop.rs` (cadences, supervise-backoff), `lifecycle.rs` (pulse loop), `registryhost.rs`.\n## DEFERRED.md\n 41|| **Quarantine operator override (`spt update apply --force` — the note-i escape hatch)**: D6-2's quarantine guard refuses re-applying a version that auto-rolled-back on this node (the `RolledBack` record is the marker; clears naturally when N+1 stages). But a version whose failure was **environmental** (a transient boot condition, not bad bytes) leaves the node **permanently allergic** to a genuinely-good version with no escape but staging a newer one. FIX = an explicit operator override (`spt update ...\n*42|| **✅ RESOLVED — shipped v0.8.3 (REQ-HAZARD-BROKER-QUIC-DEADLINE, KNOWN-HAZARDS 7.8).** ~~Broker-side bound on brain-waiting QUIC ops (the pump-IPC-deadline B-half)~~: the brain-side deadline (REQ-HAZARD-PUMP-IPC-DEADLINE, KNOWN-HAZARDS 7.6) stops the single-threaded pump wedging when a peer black-holes, by bounding the pump's brain-IPC reads and escalating a TimedOut to a supervised restart. But the ROOT cause is broker-side: the broker's `net_open_stream` / `net_stream_send` / `net_dial` handlers m...\n 43|| **Durable in-daemon alarm scheduler (one-shot deadline machinery — the Q4/V3 deferral)**: the broker/brain restoration (ADR-0018 Q4) fixes the *rule* for one-shot scheduled events — persist the absolute `target-time` at creation; every brain start (update *or* crash) reads it and fires-if-due; **never reset on crash** (a user's \"remind me at 3pm\" is a commitment that must outlive any restart). But it deliberately does **not** build the machinery: the spt-core daemon has **no one-shot consumer today...\n 44|| **Autonomous session-digest freshness + Option-C re-home (M9 ADR-0008 amendment deferral)**: M9 re-founded the digest as an ON-DEMAND projection of normalized session logs (`spt-term::projection`; `spt-daemon::digest::project_endpoint_digest`) — snapshot-pull and the structured-delta-stream CONTRACT ship, deltas driven by pulls / `api digest-entry` pushes. Two pieces deferred, both because they have **no consumer yet** (Shell=M10, GUI/frontend pane, Gateway agent-window — none exist): (a) the **aut...\n 45|| **Two-origin `owl_message` context-tap subtype refinement (M10 ADR-0019 deferral)**: the two-origin merge's owl-message producer (`spt::api::startup` `deliver` closure) taps the live-agent **`emit` chokepoint**, so EVERY delivered frame — including resurfaced **notif** frames and shell-context rows — is recorded as a single coarse `owl_message` context-injection entry (`context_kind:\"owl_message\"`). The data is not wrong (a notif IS injected context), only coarsely labeled; the digest is a glanceab...\n## KNOWN-HAZARDS.md\n 326|- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old c...\n*327|- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/0...\n 328|- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.\n 329|- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.\n 330|<!-- [doc->REQ-HAZARD-BROKER-PROCESS-ISOLATION] -->\n...\n 356|### 6.11 Brain respawn must exec the APPLIED bytes, not the renamed old binary (Linux `current_exe` follows the apply-rename; readiness ≠ new-bytes) `[REQ-HAZARD-BRAIN-RESPAWN-PATH]`\n*357|- **Failure:** the broker respawns the brain candidate from `std::env::current_exe()` resolved **per spawn** (`brainproc.rs:817`). `spt update apply` swaps the binary by renaming the running file `spt` → `spt.old-N` and writing the new bytes at `spt`. On **Linux**, `current_exe()` = `readlink(/proc/self/exe)` is **inode-tracking** and follows the rename to `.old-N`, so the resident broker respawns the brain onto the **OLD** bytes — the brain comes up ready (readiness passes), the trial **promotes**, ...\n 358|- **Invariant:** the candidate-binary default is the canonical exe path **captured once at broker start** (before any `apply` can rename under the process), never a per-spawn `current_exe()` — giving Linux the path-at-start semantics Windows already had. AND promotion is **bytes-gated**: a trial promotes only if the candidate's stamped `brain.ready` `exe_hash` equals the staged artifact's hash for this platform; a mismatch is a failed trial → auto-rollback + loud notif (readiness alone is not proof t...\n 359|- **spt-core mapping:** v0.4.2 fix (`V042-PLAN.md`). Half 1 = `spawn_brain_supervisor` canonical-exe capture threaded into `spawn_brain_child`'s `None` default; Half 2 = the promotion bytes-gate in `supervise_brain`'s `Promoted` arm (`TrialEnv::ready_exe_hash` + `staged_artifact_hash`). The rollback path (`Some(.old-N)` selection) is unchanged. Sibling of 6.7 — the broker *process* is correct; the bytes it respawned the brain ONTO were not.\n 360|- **Origin:** ADR-0018 Q3 silently assumed `current_exe()` path-string semantics; surfaced by the v0.4.1 fleet-roll `exe_hash` bytes assert (agents `todlando` + `doyle` + `deployah`, 2026-06-11). ADR-0018 Q3 amended.\n...\n 423|<!-- [doc->REQ-HAZARD-BROKER-SEED-WIRE-SKEW] -->\n*424|- **Failure:** the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004's no-terminate-during-update pillar forbids auto-killing it — 6.7). A self-update that changes a daemon-state WIRE FORMAT — e.g. the v0.9.0 adapter-agnostic `Seed` (the `adapter` field dropped) — therefore lands a NEW-version CLI talking to the STILL-RESIDENT OLD broker. The old broker cannot deserialize the new `Seed` (its formerly-required `adapter` is absent), so it drops the seed-...\n*425|- **Invariant:** (a) spt-core surfaces an ACTIONABLE diagnostic on the seed-ack EOF — naming the stale-broker cause + the fix (`spt daemon stop`; the broker restarts on the next `spt api` call) — never the bare io error (scoped to `UnexpectedEof` on the seed-ack path, so it never mis-fires on an unrelated error like a refused connect). (b) A daemon-state wire change requires a DELIBERATE full broker restart; this is NOT automatic — ADR-0004 forbids auto-killing the resident broker, and a brain-only...\n 426|- **spt-core mapping:** `startup::seed_fail_message` (the `UnexpectedEof` → actionable-hint branch) fired from `cmd_seed`'s `put_seed` error arm; the broker's seed-control residency (`seedmap::serve_seed_control`, held across brain restarts — 6.7). Unit: `seed_fail_eof_gives_actionable_stale_broker_hint` (EOF → the `spt daemon stop` hint naming the stale broker; a non-EOF kind → the plain message, no mis-fire).\n 427|- **Source:** perri PREP-4 FINDING 1 (v0.9.0 dogfood) → v0.9.1.\n 428|\n...\n 499|- **Failure (doyle instrumented RACEDIAG repro, kitsubito):** on a brain-restart re-serve the handoff brain registered as controller on the SAME session TWICE over the SAME socket — `Brain::handoff` eagerly `subscribe(prior.next_seq=1)` → `become_controller(from_seq=1)`, spawning writer-A (writes seq 1); then `serve_attach` re-handled the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, spawning writer-B (writes 0,1). `become_controller` dropped the prior `Co...\n*500|- **Invariant:** on a single brain↔broker connection exactly ONE `controller_writer` is ever the LIVE writer; a SUPERSEDED writer writes no further frames after the epoch bump it observes; and **every `controller_writer` emits a strictly ASCENDING seq stream** (sorted initial batch + ascending live frames). The CORRECTNESS guarantee that falls out: a snap-above consumer over any interleaving of ascending writers — where the surviving writer (`serve_attach`'s `attach_as(sid,0)`) offers the COMPLETE ra...\n 501|- **spt-core mapping:** impl `brain.rs` `Brain::handoff` (KEEP the eager `subscribe`; seed `session_cursors`) + `Brain::subscribe_with` (resume-mode dedup-cursor reset to `from_seq`, shared by `attach`/`attach_as` — the operator-stream boundary fix) + `broker.rs` `OutputLog.controller_epoch: Arc<AtomicU64>` / `become_controller` (atomic `fetch_add`, passes the new epoch + `Arc::clone` into the writer) / `controller_writer` (epoch gate read UNDER the lock on both loops) / `mark_controller_gone` + the `C...\n 502|- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20; design corrected across two gate rounds — fix #1 reverted, then the kitsubito RACEDIAG pinned the residual to the consumer-side operator-stream boundary, fixed by the `subscribe_with` cursor reset on `attach_as` re-subscribe). The last v0.13.0 ship-blocker.\n 503|\n...\n 605|<!-- [doc->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON] -->\n*606|- **Failure (paid-for class):** a brain-only self-update restarts the BRAIN but keeps the BROKER process — and every one of its `Mutex`es — ALIVE by design (REQ-UPD-3). So a single panic while another thread held a broker-resident lock POISONS it permanently: the next `.lock().unwrap()` panics too, kills its per-conn reply thread, and EVERY subsequent attach silently deadlines (`spt rc` → \"brain IPC read deadline elapsed\") while non-locked ops keep working. The effect journal (bug #16, `[REQ-HAZARD...\n 607|- **Invariant:** NO broker-resident lock reachable from serve/dispatch may use a bare `.lock().unwrap()`. Recover via the shared `recover` (`into_inner` — safe for the short, coherent-on-recovery map ops of `sessions`/`pair_holds`) or, for the `OutputLog` ring, `recover_log` — which adds a COHERENCE CLAMP on the recovery path: a panic mid-`append` can leave the ring torn (over-cap, a last seq not below `next_seq`, non-monotonic), and serving those bytes risks garbage, so `clamp_or_reset` cheap-checks...\n 608|- **spt-core mapping:** `broker::recover` / `broker::recover_log` / `OutputLog::clamp_or_reset`. unit = a poisoned session-map recovers to a usable guard (the next attach still opens), a torn ring clamps/resets-empty only when torn (coherent untouched, `next_seq` preserved), and `recover_log` wires the clamp onto the poison branch — each RED-first against a bare `.lock().unwrap()` / an un-clamped `into_inner`. int (scripted panic-under-lock during concurrent attach → next attach opens) deferred to th...\n 609|- **Source:** REMOTE-TRUTH B-1 (pivoted — the triage's named floor sites were already closed by `[REQ-HAZARD-INJECT-WORKER-POISON]`; the surviving attach-path lock class is the real root), doyle B-1 ruling (recover all three via one helper + the OutputLog ring coherence clamp).\n## NEXT-MILESTONE-BUG-TRIAGE.md\n 173|`spt rc eel-a` → `RC_FAIL:eel-a: attach request: brain IPC read deadline elapsed`. Worked shortly after creation. **LIVE EVIDENCE (doyle, this box):** endpoint list/daemon show eel-a `ready=true alive=true` (alive is BIND-gated — set at api bind, does NOT reflect live brain health), yet the attach IPC times out → the brain isn't servicing IPC (wedged/dead) while the perch still reads alive. daemon pid 19180. **STRONG LEAD:** the v0.18.0 `spt update apply` daemon/brain RESPAWN (done on this box earl...\n*174|- **Root cause (agent E, conf M-mechanism-H): broker-resident shared-state corruption from the self-update brain-RESPAWN — NOT the brain itself.** The only deadline-bounded call in request_attach is `net_open_stream` (rc.rs:1086, awaits NetStreamOpened under ~10s); error minted brain.rs:1442 = broker's per-conn serve thread sent NO frame for KIND_NET_STREAM_OPEN. DISCRIMINATOR: on the same rc conn, KIND_SESSIONS + KIND_NET_DIAL_LOOPBACK already SUCCEEDED (broker pid 19180 alive, thread reached attach) ...\n 175|\n 176|### G-#17 — no digest for eel-a  · STATUS: SHIPPED CLOSED — W6 env-read @9305d19 (REQ-DIGEST-PROFILE-ENV) + W6b fetcher-strategy @b9e5ce9 (REQ-DIGEST-FETCHER-STRATEGY)\n 177|> **W6** shipped the harness-agnostic `[env] direction=\"read\"` seam (capture-at-bind → info.json.read_env → digest fill_template keys → `value` fallback → `~` expansion → skip-diagnose). **W6b** closed the `{project}` design-gate per doyle's CONTEXT-symmetry ruling: `[digest]` gained a `fetcher` strategy mirroring `[history]`'s — the ADAPTER's extractor locates+reads+emits (no spt-core pre-read, no `{project}` slug), fed only harness-neutral `{session_id}`+`{cwd}`(=info.json.cwd)+captured rea...\n## NEXT-MILESTONE-PICKER-TRIAGE.md\n 124|legs (old running image; F-025's exact dormancy class). Operator updated + bounced the\n*125|daemon; doyle re-verified on-node: `spt 0.21.0`, **`broker image: 0.21.0 (matches\n 126|installed)`** (first field use of the F-025 surface), ball-b info.json clean\n 127|(`controlled:false`). Gossip refreshes on pump cadence. NOT a code defect — closed.\n 128|\n## NEXT-MILESTONE-REMOTETRUTH-TRIAGE.md\n 149|**B-1 — poison-proof the broker floor locks (the wedge itself; top-ranked\n*150|root).** Brain-only update keeps broker + QUIC conns + ControllerSink alive\n 151|by design (REQ-UPD-3); E-#16 fixed journal poison + loopback bound; the\n 152|surviving class is the inject/controller floor Mutexes with bare\n 153|`.lock().unwrap()` — broker.rs:1163 (flush_inject_floor), :1297 (inject\n...\n 160|concurrent attach; next attach must still open. KNOWN-HAZARDS entry rider\n*161|(brain-only update + poisoned broker-resident lock strands serve/attach).\n 162|**B-1 PIVOT (doyle ruling, 2026-07-05, build sweep — 4th triage-drift this\n 163|wave):** the 3 named floor sites were ALREADY poison-proofed post-triage\n 164|(`lock_floor()` recover helper, REQ-HAZARD-INJECT-WORKER-POISON, red-first\n 165|covered) — B-1-as-specified is closed. The SURVIVING members of the same\n*166|hazard class (a panic under a broker-resident lock during brain-only-update\n 167|churn permanently wedges serve/attach) are the attach/serve/dispatch-path\n 168|bare `.lock().unwrap()`s: `sessions` Mutex<HashMap> ×18, per-session\n 169|`OutputLog` Mutex ×7, `pair_holds` Mutex ×4. B-1 pivots to poison-proof\n...\n 186|(certain): control reap gates on `!has_session` (livehost.rs:749,\n*187|REQ-PRESENCE-CONTROL-REAP-ON-EXIT); brain-only update keeps the session, so a\n 188|SEVERED remote controller's sink is never reaped. Fix: detect a\n 189|ControllerSink whose writer/stream is disconnected → `clear_controller`\n 190|(broker.rs:635 seam + mark_controller_gone :645), boot-tick + detach-detect\n...\n 197|**persisted stale stamps**: `controlled`/`driven_by` records that outlive a\n*198|severed controller while the session survives (brain-only update, wedge, or\n 199|an idle-severed QUIC leg), because the livehost reap arm gates on\n 200|`!has_session` and nothing ever re-derives the stamp from broker truth.\n 201|Wire-level severance detection stays with the EXISTING paths (conn EOF,\n## PUMP-W2-DISPATCH-RULING.md\n 39|\n*40|**Design-check verdict: APPROVED.** All 5 rulings honored; premise grounded (D4c `PresenceLog` string-kind forward-compat msg.rs:904, §V4 stagger, ADR-0034 `op_id`/`minter` dedup); the re-pointed IPC-deadline asserts BOTH directions (dead peer never restarts / dead broker STILL does — the check I required). Ready to mint `REQ-PUMP-PEER-ISOLATION` + build POST-W1-gate, in a `.worktrees/` worktree (NOT the shared main checkout).\n 41|\n 42|- **Q1 — round-deadline value:** a **DERIVED CONST** = `W1_final_bound + fixed margin (~2s)`, NOT a config knob (no operator-facing tuning surface for an internal timing detail — a knob is a mis-tune/maintenance liability). Reference W1's bound constant directly; **pin the value when W1 lands its final bound** (genuine W1-before-W2 dependency — don't hardcode 10s now). Margin covers event IPC transit + drain scheduling + slack for the concurrent dials to all resolve (~one bound, not N×bound).\n 43|- **Q2 — semaphore home:** **NetHost-SHARED** (not pump-scoped). It guards the broker's iroh connection resource, which lives at the NetHost, and protects ALL dial callers (pump + shellwake + pairing) from a concurrent-dial storm — a pump-scoped limiter leaves the others unprotected + duplicates the concern. **CAVEAT (load-bearing):** the existing BLOCKING `dial()` MUST acquire the SAME permit too — otherwise the cap only bounds `submit_dial` while `net_dial` piles on unbounded on top, defeating th...\n## THE-FORKENING-TRIAGE.md\n 87|  order). Core already current → adapters only. `--core-only`/`-c` skips the\n*88|  adapters leg. Invoker survives by construction (brain-only cycle) — no\n 89|  re-run machinery.\n 90|- `spt update --restart` = fetch → **adapters** → `apply --finish` LAST\n 91|  (lethal-leg-last, ruled: completes from any invoking context; a finish\n## W6-GATE-VERDICT.md\n 29|- Spot-checked DTO field names against source (3 samples, all match): daemon-status\n*30|  `stall_evict_count/last_ms/pump_heartbeat_ms/broker_stale` (cli.rs:3956-3980),\n 31|  `AdapterVersionJson{adapter,version}` (cli.rs:8641), digest `input_seq`/`after_predates_window`.\n 32|\n 33|### 3. SPT_INJECT_VERIFY_ECHO CC-manifest wiring — CLOSED adapter-side (perri) ✓\n\nShowing files 1-20 of 27+. Use skip=20 for the next page, or narrow paths/pattern.\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"crates/spt-daemon/src, crates/spt/src, traceable-reqs.toml, CONTEXT.md, docs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":88,"fileCount":20,"files":["crates/spt-daemon/src/applyhost.rs","crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/lib.rs","crates/spt-daemon/src/msg.rs","crates/spt-daemon/src/notif.rs","crates/spt-daemon/src/update.rs","crates/spt/src/api/startup.rs","crates/spt/src/cli.rs","traceable-reqs.toml","CONTEXT.md","docs/BROKER-BRAIN-SPLIT-RESTORATION.md","docs/DEFERRED.md","docs/KNOWN-HAZARDS.md","docs/NEXT-MILESTONE-BUG-TRIAGE.md","docs/NEXT-MILESTONE-PICKER-TRIAGE.md","docs/NEXT-MILESTONE-REMOTETRUTH-TRIAGE.md","docs/PUMP-W2-DISPATCH-RULING.md","docs/THE-FORKENING-TRIAGE.md","docs/W6-GATE-VERDICT.md"],"fileMatches":[{"path":"crates/spt-daemon/src/applyhost.rs","count":15},{"path":"crates/spt-daemon/src/brain.rs","count":1},{"path":"crates/spt-daemon/src/broker.rs","count":3},{"path":"crates/spt-daemon/src/lib.rs","count":4},{"path":"crates/spt-daemon/src/msg.rs","count":1},{"path":"crates/spt-daemon/src/notif.rs","count":2},{"path":"crates/spt-daemon/src/update.rs","count":20},{"path":"crates/spt/src/api/startup.rs","count":1},{"path":"crates/spt/src/cli.rs","count":7},{"path":"traceable-reqs.toml","count":12},{"path":"CONTEXT.md","count":1},{"path":"docs/BROKER-BRAIN-SPLIT-RESTORATION.md","count":4},{"path":"docs/DEFERRED.md","count":1},{"path":"docs/KNOWN-HAZARDS.md","count":6},{"path":"docs/NEXT-MILESTONE-BUG-TRIAGE.md","count":1},{"path":"docs/NEXT-MILESTONE-PICKER-TRIAGE.md","count":1},{"path":"docs/NEXT-MILESTONE-REMOTETRUTH-TRIAGE.md","count":5},{"path":"docs/PUMP-W2-DISPATCH-RULING.md","count":1},{"path":"docs/THE-FORKENING-TRIAGE.md","count":1},{"path":"docs/W6-GATE-VERDICT.md","count":1}],"truncated":true,"fileLimitReached":20,"perFileLimitReached":20,"displayContent":"# traceable-reqs.toml\n  413│title = \"No endpoint process terminates/suspends during self-update\"\n *414│required_stages = [\"impl\", \"unit\", \"int\"]  # int RE-POINTED at restoration D7-1 (ADR-0018 V5) to the PROCESS-level survival E2E (crates/spt/tests/brain_survive.rs: no endpoint terminates across a brain-only update at the PROCESS level — a real brain-child respawn onto a swapped on-disk binary, the PTY child's pid unchanged + held-AND-functional + the QUIC conn intact). Was M3c-C0 brain_swap.rs (the in-process Brain::handoff closure shape — regression-masked per ADR-0018); that tag removed in the same...\n  415│[[requirements]]\n  416│id = \"REQ-UPD-4\"\n  417│title = \"Update gated on user confirmation by default; opt-in full-auto\"\n     │...\n  495│id = \"REQ-HOST-RUN-2\"\n *496│title = \"Project-scoped working directory for spt-hosted bringup: `spt endpoint run` lands the broker-spawned harness PTY in the user's PROJECT cwd, not the daemon's, via an additive `SpawnReq.cwd` field carried through the broker PTY spawn (portable-pty CommandBuilder cwd). N-1-safe wire change (additive, defaulted). Required because the consumer (Claude Code) is project-scoped: broker-inherited cwd = the daemon's cwd = the wrong `.claude`, wrong session history, wrong digest source; `cc <id>` at a proj...\n  497│required_stages = [\"impl\", \"unit\", \"int\"]  # activated M12-W1.5: impl = additive SpawnReq.cwd + PtySession::spawn_program_in (portable-pty CommandBuilder cwd) + broker dispatch_spawn honors it + harnesshost/cli thread project cwd (std::env::current_dir); unit = spawn_program_in_lands_the_child_in_the_requested_cwd (spt-term); int = broker_spawns_the_pty_child_in_the_requested_cwd (wire field → broker spawn → PTY cwd, end to end)\n  498│# ── M12 Wave 2: interactive `spt endpoint run` picker (M12-PLAN.md Wave 2 / M12-ENDPOINT-RUN-PICKER.md;\n  499│#    doyle ruling M12-W2-RULING.md). Pure front-end over existing surfaces — no second bringup path\n     │...\n  954│id = \"REQ-HAZARD-BROKER-SEED-WIRE-SKEW\"\n *955│title = \"A daemon-state wire-format change (e.g. the v0.9.0 adapter-agnostic Seed) does NOT take effect until a DELIBERATE full broker restart: the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004 no-terminate-during-update forbids auto-killing it), so a NEW-version CLI talking to a still-resident OLD broker fails the seed handshake — the old broker cannot deserialize the new Seed (its formerly-required `adapter` field is gone) and drops the conn without ...\n  956│required_stages = [\"doc\", \"impl\", \"unit\"]  # activated v0.9.1: doc = KNOWN-HAZARDS entry. impl = cmd_seed appends the actionable stale-broker hint when put_seed's seed-ack read returns UnexpectedEof (scoped to that path, not unrelated EOFs). unit = a seed-control server that reads the PUT frame then drops the conn without acking (the old-broker deser-fail close) → cmd_seed surfaces the `spt daemon stop` hint, not a bare buffer error\n  957│[[requirements]]\n  958│id = \"REQ-HAZARD-SUDO-SECURE-PATH\"\n     │...\n  987│id = \"REQ-HAZARD-BROKER-PROCESS-ISOLATION\"\n *988│title = \"Broker and brain are separate processes: the broker runs as its own long-lived per-machine process that survives every brain restart, so a routine (brain-only) self-update restarts the brain onto the swapped binary while every hosted endpoint (PTY child, live QUIC conn, listening socket) stays untouched at the PROCESS level. The in-process-thread broker (daemon.rs:165-170) is a regression that silently unrealizes REQ-UPD-3 — apply degrades to an in-process Brain::handoff no-op and new code doe...\n  989│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # activated restoration D1 (ADR-0018 Q2/Q3): doc = KNOWN-HAZARDS 6.7 + ADR-0018 + design doc; impl = brainproc.rs (brain child entry + broker-side process supervisor) + daemon.rs spawn-brain-supervisor + the hidden `daemon brain` CLI entry; unit = brainproc supervisor respawn/backoff table + the real-binary brain_split.rs process-level smoke (broker survives a brain kill + respawns it). int ACTIVATED restoration D7 = the productionized SPIKE-01/03 process...\n  990│[[requirements]]\n  991│id = \"REQ-HAZARD-ROLLBACK-STATE-COMPAT\"\n     │...\n 1217│id = \"REQ-HAZARD-ATTACH-WEDGE\"\n*1218│title = \"A legitimately dead PTY child (real crash/kill) + an undrained operator pump must NOT wedge the broker for all other clients. ROOT (v0.12.0 real-harness defect): loopback attach output is a blocking write_all into a bounded 64KB tokio duplex (nethost.rs:1040,1090); when the operator's rc pump stops draining (tab closed) the buffer fills and write_all blocks forever (the 'loopback never hangs' assumption at nethost.rs:1103 is false), parking a worker in the 2-worker net runtime (nethost.rs:640); ...\n 1219│required_stages = [\"int\"]  # ACTIVATED v0.12.1 L2 (todlando) as PROVE-DON'T-CHANGE (like the M11-W4 capstone): the post-L0 code ALREADY prevents the wedge, so a test proves it and NO impl/unit code changes. The original \"loopback write_all blocks forever → parks the 2-worker net runtime\" root is STALE: (1) serve_attach forwards via fire-and-forget net_stream_send(op_id=None) → broker-side send_stream is ALREADY bounded by bounded_block_on (BROKER-QUIC-DEADLINE, 10s), not forever; (2) the loopback dup...\n 1220│[[requirements]]\n 1221│id = \"REQ-PICKER-HISTORY-FRESH\"\n     │...\n 1407│id = \"REQ-HAZARD-CONTROLLER-WRITER-REORDER\"\n*1408│title = \"Two `controller_writer` threads must never race ONE brain↔broker connection's socket. ROOT (doyle, instrumented RACEDIAG repro on kitsubito): on a brain-restart re-serve the handoff brain registers as controller on the SAME session TWICE over the SAME `Brain::conn` socket — (1) `Brain::handoff` eagerly `subscribe(prior.session_id, prior.next_seq=1)` → `become_controller(from_seq=1)`, initial=[1], spawns writer-A (writes seq 1); (2) `serve_attach` re-handles the replayed `Request{from_seq:0...\n 1409│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED v0.13.0 P1c (todlando, the last v0.13.0 ship-blocker — pre-existing flaky resume race that gates green-both-runners; doyle root-caused + designed, doyle gates). doc = KNOWN-HAZARDS entry (the reorder + the single-live-writer-per-connection invariant + the two compounding defects) + CONTEXT note. impl = (1) Brain::handoff KEEPS its eager subscribe(prior.session_id, prior.next_seq) [fix #1 REVERTED — load-bearing resume] AND seeds session_cu...\n 1410│\n 1411│# ── P1c follow-up SEED (doyle proposed, todlando gated, doyle concurred 2026-06-20): the structural per-connection-floor guard — inactive, activate-don't-pre-fail ──\n     │...\n 1558│id = \"REQ-UPDATE-RUNNING-IMAGE-SURFACE\"\n*1559│title = \"`spt` surfaces the RUNNING broker image version beside the on-disk version so an updated-looking node reveals broker-side dormancy. ROOT (F-025): `spt update apply` restarts the BRAIN only (ADR-0018 D3-3) — the BROKER process survives and keeps running its pre-apply compiled image, so every broker-side surface of a freshly-applied release (the F015B live-apply matcher, dispatch inject legs, etc.) is silently dormant until a full daemon bounce, with nothing in the CLI revealing the split. FIX: ...\n*1560│required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED F-025 item 1a + final-wave int (todlando). impl = KIND_BROKER_IMAGE/_REPLY + BrokerImageReply (msg.rs) → broker self-report dispatch_broker_image from env!(\"CARGO_PKG_VERSION\") (broker.rs) + Brain::broker_image_version client reading the reply, KIND_ERROR (old broker's `unknown command kind`) → None (brain.rs) + cmd_daemon_status queries both net-status and the image on ONE handle and renders render_broker_image_line beside the installed version;...\n 1561│[[requirements]]\n 1562│id = \"REQ-UPDATE-APPLY-RESTART-NOTICE\"\n 1563│title = \"`spt update apply` prints a LOUD restart-required notice whenever the surviving broker will keep running the pre-apply image (which, until broker-restart choreography exists, is ALWAYS on a successful apply). Public wording, no internal CODE:RESULT markers (composes with REQ-ADAPTER-UPDATE-MESSAGE / the update-apply-confident-message rule) — name the user-visible CONSEQUENCE ('daemon-coordinated features run the previous version until the daemon restarts'), not the broker/brain internals. Comp...\n*1564│required_stages = [\"impl\", \"unit\"]  # ACTIVATED F-025 item 1b (todlando). impl = render_applied_message appends restart_required_notice() (cli.rs) on every successful apply — names the consequence (daemon-coordinated features run the previous version), the restart recovery (`spt daemon stop`/`start`), and points at the `spt daemon status` surface (1a); public wording, no broker/brain internals. unit = applied_message_appends_restart_required_notice (keeps the confident head + names consequence + restar...\n 1565│[[requirements]]\n 1566│id = \"REQ-UPDATE-APPLY-ALREADY-APPLIED\"\n 1567│title = \"`spt update apply` classifies an already-staged / already-applied state as a friendly exit-0 no-op instead of dying at the binary-aside rename with 'Access is denied (os error 5)'. ROOT (F-025): a second apply on an already-applied staged version reaches the two-phase binary-aside rename and fails os-error-5, reading as a hard failure when the machine is simply up to date. FIX: apply gains the same pure classifier `fetch` got in v0.18.0 (REQ-UPDATE-FETCH-CURRENT-UX) — already-applied → clear...\n     │...\n 1980│id = \"REQ-HAZARD-BROKER-FLOOR-LOCK-POISON\"\n*1981│title = \"B-1 (REMOTE-TRUTH triage §B-1, PIVOTED @e5eb99a): NO bare `.lock().unwrap()` on a broker-resident lock reachable from serve/dispatch — a brain-only self-update keeps the broker + all its Mutexes ALIVE (REQ-UPD-3), so a single panic under one poisons it PERMANENTLY: the next `.lock().unwrap()` panics, kills its per-conn reply thread, and EVERY subsequent attach silently deadlines ('brain IPC read deadline elapsed') while non-locked ops keep working. TRIAGE-DRIFT (sweep-dispatch-site-counts dis...\n 1982│required_stages = [\"doc\", \"impl\", \"unit\"]  # ACTIVATED REMOTE-TRUTH B-1 PIVOTED (todlando). doc = KNOWN-HAZARDS.md 7.33 (class-level: NO bare .lock().unwrap() on a serve/dispatch-reachable broker-resident lock — recover/recover_log or documented fail-fast). impl = broker::recover<T> (into_inner shared helper) applied at self.sessions ×18 + sessions_exit + pair_holds ×4 (23), broker::recover_log (recover + coherence clamp on the poison branch) at the 11 OutputLog ring lock sites, OutputLog::clamp_or_r...\n 1983│[[requirements]]\n 1984│id = \"REQ-CONTROLLER-LIVENESS-REAP\"\n*1985│title = \"B-2 (REMOTE-TRUTH triage §B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals — the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a tr...\n 1986│required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REMOTE-TRUTH B-2 REDUCED (todlando); int ACTIVATED on the green [twohost] rig run 28772170556 @279df99 (2026-07-06). impl = OutputLog::reap_dead_controller (broker.rs: drop a controller whose _writer.is_finished() in-memory ONLY, no stamp — lock-safe under the KIND_SESSIONS snapshot log lock) + the snapshot-closure call site (recover_log(&log) made mut; reap BEFORE reading controller_by/has_controller so the reply + the existing off-lock converge_p...\n 1987│[[requirements]]\n 1988│id = \"REQ-ADAPTER-FLOOR-ENFORCE\"\n     │...\n 2386│id = \"REQ-UPDATE-DEFAULT-COMPOSITE\"\n*2387│title = \"THE-FORKENING W4 (operator-grilled 2026-07-14): plain `spt update` = `update fetch --apply` THEN `update adapters` (core-first doctrine order); when core is already current the core leg no-ops and ONLY adapters update; `--core-only`/`-c` skips the adapters leg. GROUNDING (operator-corrected, code-confirmed): fetch --apply cycles the BRAIN only — broker + PTYs survive (apply_staged applyhost.rs:303; the restart-required text is a NOTICE, cli.rs:4615, not behavior) — so the composite's invokin...\n 2388│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2389│\n 2390│[[requirements]]\n\n# CONTEXT.md\n 582│\n*583│**daemon refresh (`spt daemon refresh`)** — restart the **brain** in place, no binary swap, broker and every held PTY untouched: the routine-update handoff path minus the swap. The recovery verb for wedged brain-held state (broken endpoint bringup, a downed hosted agent) that previously required a full daemon bounce. <!-- [doc->REQ-DAEMON-REFRESH] -->\n 584│\n 585│**project index** — a node's endpoint→project attribution is DERIVED state, held as a **persistent materialized index** (ADR-0037): `spt-store` owns the versioned format + read path (daemon-offline reads = last persisted snapshot); the **daemon is the sole single-flight writer** (load-at-start, ready-without-warm, background batched reconcile, atomic replace, coalesced event-driven invalidation keyed on branch-tip fingerprints, last-known-good on failure). Readers — list, picker, endpoint-info, hoo...\n 586│\n\n# crates/\n\n## spt-daemon/src/\n### applyhost.rs\n   20│//! 2. **Classify** against the running broker's ABI. Only\n  *21│//!    [`UpdateClass::BrainOnly`] applies in M5 — the broker-touching classes\n   22│//!    refuse typed, the artifact stays staged (their consent-and-scheduling\n   23│//!    orchestration is post-M5; ADR-0004 §A).\n   24│//! 3. **Swap the binary**: the current executable steps aside\n     │...\n   49│use crate::release::{current_platform, VerifyPolicy};\n  *50│use crate::update::{plan_verified, plan_verified_update_set, BrokerAbi, UpdateClass};\n   51│\n   52│/// What [`apply_staged`] did — loud + typed, never silent.\n   53│#[derive(Debug, Clone, PartialEq, Eq)]\n     │...\n   68│    Rejected(String),\n  *69│    /// Verified but not brain-only — the M5 apply surface refuses the\n   70│    /// broker-touching classes typed; the artifact stays staged.\n  *71│    RefusedClass(UpdateClass),\n   72│    /// Refused: this version was auto-rolled-back on this node (a prior trial\n   73│    /// failed readiness) and is **quarantined** — never re-applied until a newer\n   74│    /// version stages or an operator forces it (the deferred escape hatch). The\n     │...\n  111│    Done(ApplyStagedOutcome),\n *112│    /// Verified brain-only bytes ready to land at the exe path.\n  113│    Proceed {\n  114│        version: u64,\n  115│        product_version: String,\n     │...\n  121│/// already-applied short-circuit, at-rest re-verification, platform guard, and\n *122│/// the brain-only class gate. Pure w.r.t. the daemon — reads only the cache +\n  123│/// keys, swaps nothing. Both the broker-handoff apply and the daemonless apply\n  124│/// run this identical gate so their fail-closed posture never diverges.\n  125│// [impl->REQ-UPD-4]\n     │...\n  236│    };\n *237│    if plan.class != UpdateClass::BrainOnly {\n  238│        return Ok(Prepared::Done(ApplyStagedOutcome::RefusedClass(plan.class)));\n  239│    }\n  240│\n     │...\n  332│    // regression: it re-attached a CLI-side subscriber while the live brain kept\n *333│    // running the OLD code). `classify` already gated this as brain-only, so the\n  334│    // broker never moves; only the brain restarts.\n  335│    //\n  336│    // A broker without a supervisor wired (`honored: false`) still swapped the\n     │...\n  577│            .broker_image_version()\n *578│            .expect(\"broker image query round-trips\");\n  579│        assert_eq!(\n  580│            v.as_deref(),\n  581│            Some(env!(\"CARGO_PKG_VERSION\")),\n     │...\n  872│    // [unit->REQ-UPD-4] the ack-driven apply end to end: a staged + verified\n *873│    // brain-only release swaps the binary (artifact lands at the exe path,\n  874│    // the original steps aside) and triggers the brain handoff with the\n  875│    // hosted endpoint untouched — the production caller the M4-D9 residual\n  876│    // promised. [unit->REQ-UPD-3]\n     │...\n  980│                out,\n *981│                ApplyStagedOutcome::RefusedClass(UpdateClass::BrokerBreaking)\n  982│            ),\n  983│            \"got {out:?}\"\n  984│        );\n     │...\n 1033│    // [unit->REQ-UPD-3] A node with no broker-hosted sessions still applies a\n*1034│    // brain-only update: there is nothing to hand off, and no endpoint can be\n 1035│    // terminated by the apply.\n 1036│    #[test]\n 1037│    fn apply_staged_without_broker_hosted_sessions_swaps_binary() {\n     │...\n 1077│                out,\n*1078│                ApplyStagedOutcome::RefusedClass(UpdateClass::BrokerBreaking)\n 1079│            ),\n 1080│            \"got {out:?}\"\n 1081│        );\n     │...\n 1086│    // [unit->REQ-UPD-4] idempotent no-op and fail-closed gates: nothing\n*1087│    // staged ⇒ NoUpdate; a non-brain-only class refuses typed with the seat\n 1088│    // untouched; a tampered at-rest artifact rejects with the seat untouched.\n 1089│    #[test]\n 1090│    fn apply_staged_gates_fail_closed_with_the_seat_untouched() {\n     │...\n 1108│                out,\n*1109│                ApplyStagedOutcome::RefusedClass(UpdateClass::BrokerBreaking)\n 1110│            ),\n 1111│            \"got {out:?}\"\n 1112│        );\n### brain.rs\n 369│    /// The eager `subscribe` IS the resume mechanism — every standalone handoff\n*370│    /// caller (the brain-only update engine, `idempotent`/`daemon_e2e`/`handoff`\n 371│    /// integration tests) replays output through it with no later `attach`. So it\n 372│    /// stays. The P1c controller-writer reorder (a `serve_attach` re-serve that\n 373│    /// ALSO subscribes from 0 over this same socket → two racing\n### broker.rs\n 1829│/// Poison-tolerant lock for ANY broker-resident mutex reachable from serve/dispatch\n*1830│/// (B-1, REQ-HAZARD-BROKER-FLOOR-LOCK-POISON). A brain-only self-update keeps the\n 1831│/// broker process — and every one of its `Mutex`es — ALIVE (REQ-UPD-3), so a single\n 1832│/// panic while another thread held one of these locks would otherwise POISON it\n 1833│/// permanently: the next `.lock().unwrap()` panics too, its per-conn reply thread\n     │...\n 3854│    /// version at build time, so a broker that SURVIVED a `spt update apply`\n*3855│    /// (brain-only restart, ADR-0018 D3-3) honestly reports the PRE-apply version\n 3856│    /// — the authoritative running-image truth the `daemon status` version surface\n 3857│    /// compares against the freshly-installed CLI. Sourced from the running\n 3858│    /// process, never inferred from disk (the disk is the half already ahead).\n     │...\n 5143│    // (the SESSIONS class — sessions/pair_holds) RECOVERS from a poison instead of\n*5144│    // panicking. Brain-only self-update keeps the broker + this lock alive; a panic\n 5145│    // under it during attach churn would poison it permanently → EVERY subsequent\n 5146│    // attach (serve/dispatch locks sessions) panics its reply thread and deadlines\n 5147│    // (\"brain IPC read deadline elapsed\") forever. `recover` (the fix) hands back a\n### lib.rs\n  43│//! **C0 (this commit)** opens M3c — the self-update engine ([`update`]): the\n *44│//! ADR-0004 §A update-class taxonomy ([`UpdateClass`] + [`classify`]) and the\n *45│//! **brain-only zero-interruption swap** ([`apply_brain_only`], [`REQ-UPD-3`]).\n *46│//! The brain-only apply *triggers* a handoff over the B2 substrate — snapshot →\n  47│//! drop → re-attach — so the live PTY child + its output stream survive the logic\n  48│//! swap untouched; it never re-implements the handoff and never touches a broker\n  49│//! resource.\n    │...\n 263│    apply_brain_only, classify, plan_update, plan_verified, plan_verified_update_set, ApplyError,\n*264│    BrokerAbi, ReleaseSpec, UpdateClass, UpdatePlan, BROKER_RESOURCE_ABI,\n 265│};\n 266│pub use wan::{receive_wan, request_wan, WanOutcome, WanRequestOutcome};\n 267│pub use xfer::{fetch_file, push_file, serve_xfer, XferServeOutcome};\n### msg.rs\n  99│pub const KIND_BROKER_IMAGE: &str = \"broker-image\";\n*100│/// Broker→brain: the broker image version reply.\n 101│pub const KIND_BROKER_IMAGE_REPLY: &str = \"broker-image-reply\";\n 102│/// Brain→broker query: the brain-subscriber stall-evict tally (LIFECYCLE-TRUTH W2\n 103│/// observability rider, REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) — how many\n### notif.rs\n  57│/// Producer kind for the auto-rollback alert (ADR-0018 Q7, restoration D6-2): a\n *58│/// brain-only update failed readiness and the supervisor rolled back to the\n  59│/// last-known-good binary. A distinct kind so a surface can style it; it rides\n  60│/// the same first-fire + **boundary-resurface** path as every other producer, so\n  61│/// an undismissed rollback row re-surfaces until the operator dismisses it\n    │...\n 425│/// Produce + first-fire the loud auto-rollback alert (restoration D6-2, ADR-0018\n*426│/// Q7): a brain-only update `v{quarantine_version}` failed to reach `ready` and\n 427│/// the supervisor rolled back to `v{running_version}`, quarantining the bad\n 428│/// version. Reuses the one producer front door ([`produce_and_first_fire`]) so\n 429│/// the row rides first-fire AND **boundary-resurface** — an undismissed rollback\n### update.rs\n  *1│//! The self-update engine (C0) — the update-class taxonomy and the brain-only\n   2│//! zero-interruption swap.\n   3│//!\n   4│//! ADR-0004 §A splits a self-update into **three classes**, each with its own\n    │...\n   6│//!\n  *7│//! - **brain-only** — the routine case. The running broker can already host the\n   8│//!   release's brain (the versioned-IPC handshake accepts it) and the broker's\n   9│//!   held-resource ABI is unchanged, so only the logic swaps. **No endpoint\n  10│//!   process terminates or suspends** ([`REQ-UPD-3`], absolute for this class) —\n    │...\n  20│//!\n *21│//! C0 implements the **classifier** ([`classify`]) and the **brain-only apply**\n  22│//! ([`apply_brain_only`]). The apply *triggers a handoff* over the existing M3b\n  23│//! substrate ([`crate::brain::Brain::handoff`]) — it does **not** re-implement\n  24│//! one (clean-room posture, `M3c-PLAN.md` §C0). The signing/rollback hardening\n    │...\n  40│/// an existing handle is carried. A pure logic change (the routine update) never\n *41│/// touches this — that is precisely what keeps the routine case *brain-only*.\n  42│pub const BROKER_RESOURCE_ABI: u32 = 1;\n  43│\n  44│/// What the currently-running broker provides — the fixed point an incoming\n    │...\n  89│#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n *90│pub enum UpdateClass {\n  91│    /// Swap logic only; the running broker hosts the new brain unchanged. No\n  92│    /// endpoint terminates ([`REQ-UPD-3`], absolute). The routine case.\n *93│    BrainOnly,\n  94│    /// The broker binary hot-swaps behind the versioned IPC (held-resource type\n  95│    /// unchanged); endpoints survive. Apply path deferred (no spike — ADR-0004 §A).\n  96│    BrokerCompatible,\n    │...\n 110│///    is at or above the running broker's floor, the handshake accepts it and\n*111│///    only logic swaps: **brain-only**. Otherwise the broker must move to a build\n 112│///    that speaks the new IPC — but since the resource type is unchanged the new\n 113│///    broker adopts the existing handles: **broker-compatible**.\n 114│// [impl->REQ-UPD-3]\n*115│pub fn classify(running: &BrokerAbi, release: &ReleaseSpec) -> UpdateClass {\n 116│    if release.broker_resource_abi != running.resource_abi {\n*117│        return UpdateClass::BrokerBreaking;\n 118│    }\n 119│    if release.brain_ipc_version >= running.min_compatible {\n*120│        UpdateClass::BrainOnly\n 121│    } else {\n*122│        UpdateClass::BrokerCompatible\n 123│    }\n 124│}\n 125│\n    │...\n 131│    /// The update class (ADR-0004 §A).\n*132│    pub class: UpdateClass,\n 133│    /// The release the plan was built for.\n 134│    pub release: ReleaseSpec,\n 135│}\n    │...\n 139│    UpdatePlan {\n*140│        class: classify(running, &release),\n 141│        release,\n 142│    }\n 143│}\n    │...\n 184│\n*185│/// Why a brain-only apply did not complete.\n 186│#[derive(Debug)]\n 187│pub enum ApplyError {\n*188│    /// The plan is not brain-only — routing it through the brain-only path would\n 189│    /// risk an endpoint, which this class must never do. Raised **before** the\n 190│    /// outgoing brain is touched. The caller routes a broker-compatible /\n 191│    /// broker-breaking plan through its own (consent-gated, scheduled) path;\n*192│    /// brain-only is the only swap C0 performs.\n*193│    NotBrainOnly(UpdateClass),\n 194│    /// The new brain failed to re-attach (the `relaunch` closure errored). The\n 195│    /// endpoint is **untouched** regardless — the broker + its hosted child\n 196│    /// persist across a failed re-attach exactly as across a brain crash — so the\n    │...\n 203│        match self {\n*204│            ApplyError::NotBrainOnly(c) => {\n 205│                write!(\n 206│                    f,\n 207│                    \"apply_brain_only refused a {c:?} plan (would risk an endpoint)\"\n    │...\n 210│            ApplyError::Relaunch(e) => {\n*211│                write!(f, \"brain-only relaunch failed (endpoint intact): {e}\")\n 212│            }\n 213│        }\n 214│    }\n    │...\n 219│        match self {\n*220│            ApplyError::NotBrainOnly(_) => None,\n 221│            ApplyError::Relaunch(e) => Some(e),\n 222│        }\n 223│    }\n\n## spt/src/\n### cli.rs\n 4160│    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n*4161│    broker_stale: Option<bool>,\n 4162│    /// LIFECYCLE-TRUTH W2 observability rider: brain-subscriber stall-evicts since\n 4163│    /// broker boot (a controller detached for a suspended/black-holed brain conn).\n 4164│    /// `None` when the daemon is down or the broker is too old to report; `Some(0)`\n     │...\n 4213│    match running {\n*4214│        Some(v) if v == installed => format!(\"broker image: {v} (matches installed)\"),\n 4215│        Some(v) => format!(\n*4216│            \"broker image: {v} (running) — installed is {installed}; the daemon is still on the \\\n 4217│             previous version, so daemon-coordinated features run {v} until it restarts. Restart \\\n 4218│             with `spt daemon stop` then `spt daemon start`.\"\n 4219│        ),\n 4220│        None => format!(\n*4221│            \"broker image: not reported (the running daemon predates version reporting, or could \\\n 4222│             not be queried) — if it is older than the installed {installed}, restart with \\\n 4223│             `spt daemon stop` then `spt daemon start` to load {installed}.\"\n 4224│        ),\n     │...\n 4388│        // queried (no false-positive true on a transient failure — doyle F-025 nit).\n*4389│        let broker_stale = (running && broker_queried)\n 4390│            .then(|| broker_image.as_deref() != Some(env!(\"CARGO_PKG_VERSION\")));\n 4391│        let pump_heartbeat_ms = if running {\n 4392│            spt_daemon::read_pump_heartbeat()\n     │...\n 4451│            broker_image,\n*4452│            broker_stale,\n 4453│            stall_evict_count,\n 4454│            stall_evict_last_ms,\n 4455│            project_index: spt_daemon::projwriter::read_stats_at(\n     │...\n 5047│    /// `update fetch --apply` — stage + install the core in one step (the\n*5048│    /// brain-only cycle: the invoking process survives by construction).\n 5049│    FetchApply,\n 5050│    /// `update fetch` — stage only; the swap waits for the Finish leg.\n 5051│    Fetch,\n\n### api/\n#### startup.rs\n 69│/// (`UnexpectedEof` = \"failed to fill whole buffer\") is the daemon-state\n*70│/// wire-skew footgun: a still-resident OLD broker (resident across a brain-only\n 71│/// self-update — ADR-0004) cannot deserialize the new Seed and drops the conn\n 72│/// without acking. That case gets the actionable cause + fix; any other error\n 73│/// kind keeps the raw message (so the hint never mis-fires on a refused connect\n\n# docs/\n## BROKER-BRAIN-SPLIT-RESTORATION.md\n  84│\n *85│Restore the two-process model so that a routine **brain-only** update (the common case per ADR-0004 §A's class taxonomy) hands off to the new executable seamlessly:\n  86│\n  87│1. no endpoint or harness child process is dropped or suspended (REQ-UPD-3),\n  88│2. internal loop timings stay consistent across the swap,\n    │...\n  91│\n *92│The broker-touching update classes (broker-compatible / broker-breaking) remain as ADR-0004 left them — out of scope here; this work is the **brain-only** path's realization.\n  93│\n  94│---\n  95│\n    │...\n 182│\n*183│**Decision/observation.** The broker spawns the brain as a **child process** (`Command::spawn`) and they communicate over the existing local socket IPC. **No `exec` is required**, so there is no Windows/Unix divergence. ConPTY (Windows) and forkpty (Linux) handles stay broker-side and never cross to the brain. Spikes 01 and 04 already proved the handoff on both OSes.\n 184│\n 185│**Reason.** This is precisely the uniformity ADR-0004:25 sought when it rejected whole-daemon live FD-passing as \"hard and platform-divergent.\" Spawn-plus-socket sidesteps it entirely.\n 186│\n    │...\n 225│- **ADR-0004** — `docs/adr/0004-single-daemon-broker-brain-split-and-self-update.md` (the decision being restored; §A update classes, §B ownership table, the spike amendments).\n*226│- **Requirements** — REQ-UPD-3 (no-endpoint-drop, brain-only), REQ-UPD-4 (staged + consent), REQ-UPD-6/8 (update-set / platform-safe), REQ-DAEMON-3/9 (auto-start / net self-heal), REQ-HAZARD-UPDATE-ROLLBACK, REQ-HAZARD-HANDOFF-ARGV-COMPAT.\n 227│- **Glossary/model** — `CONTEXT.md` daemon / broker / brain (incl. the one-broker-per-machine clause added this session).\n 228│- **Spikes** — `docs/spikes/SPIKE-01-broker-handoff.md`, `-03-quic-survival`, `-04-forkpty-parity`, `-05-restart-stress`, `-06-idempotent-boundary` (proofs that the two-process handoff works on both OSes).\n 229│- **Key source** — `daemon.rs` (`run`, net consumers, self-heal, digest hub, shellwake, seed-control), `broker.rs` (`OutputLog`, `bind`, `EffectJournal`), `brain.rs` (`BrainState`, `cold_start`/`handoff`/`snapshot`), `applyhost.rs` (`apply_staged`, `connect_retry`, record-before-handoff), `update.rs` (classes, `classify`, `apply_brain_only`), `peerloop.rs` (cadences, supervise-backoff), `lifecycle.rs` (pulse loop), `registryhost.rs`.\n## DEFERRED.md\n 41│| **Quarantine operator override (`spt update apply --force` — the note-i escape hatch)**: D6-2's quarantine guard refuses re-applying a version that auto-rolled-back on this node (the `RolledBack` record is the marker; clears naturally when N+1 stages). But a version whose failure was **environmental** (a transient boot condition, not bad bytes) leaves the node **permanently allergic** to a genuinely-good version with no escape but staging a newer one. FIX = an explicit operator override (`spt update ...\n*42│| **✅ RESOLVED — shipped v0.8.3 (REQ-HAZARD-BROKER-QUIC-DEADLINE, KNOWN-HAZARDS 7.8).** ~~Broker-side bound on brain-waiting QUIC ops (the pump-IPC-deadline B-half)~~: the brain-side deadline (REQ-HAZARD-PUMP-IPC-DEADLINE, KNOWN-HAZARDS 7.6) stops the single-threaded pump wedging when a peer black-holes, by bounding the pump's brain-IPC reads and escalating a TimedOut to a supervised restart. But the ROOT cause is broker-side: the broker's `net_open_stream` / `net_stream_send` / `net_dial` handlers m...\n 43│| **Durable in-daemon alarm scheduler (one-shot deadline machinery — the Q4/V3 deferral)**: the broker/brain restoration (ADR-0018 Q4) fixes the *rule* for one-shot scheduled events — persist the absolute `target-time` at creation; every brain start (update *or* crash) reads it and fires-if-due; **never reset on crash** (a user's \"remind me at 3pm\" is a commitment that must outlive any restart). But it deliberately does **not** build the machinery: the spt-core daemon has **no one-shot consumer today...\n 44│| **Autonomous session-digest freshness + Option-C re-home (M9 ADR-0008 amendment deferral)**: M9 re-founded the digest as an ON-DEMAND projection of normalized session logs (`spt-term::projection`; `spt-daemon::digest::project_endpoint_digest`) — snapshot-pull and the structured-delta-stream CONTRACT ship, deltas driven by pulls / `api digest-entry` pushes. Two pieces deferred, both because they have **no consumer yet** (Shell=M10, GUI/frontend pane, Gateway agent-window — none exist): (a) the **aut...\n 45│| **Two-origin `owl_message` context-tap subtype refinement (M10 ADR-0019 deferral)**: the two-origin merge's owl-message producer (`spt::api::startup` `deliver` closure) taps the live-agent **`emit` chokepoint**, so EVERY delivered frame — including resurfaced **notif** frames and shell-context rows — is recorded as a single coarse `owl_message` context-injection entry (`context_kind:\"owl_message\"`). The data is not wrong (a notif IS injected context), only coarsely labeled; the digest is a glanceab...\n## KNOWN-HAZARDS.md\n 326│- **Failure:** the daemon hosts the broker as a background *thread* in the single `spt daemon` process (`daemon.rs:165-170`, `Arc<Broker>` + `thread::spawn(serve)`) instead of a separate process. A brain restart onto a swapped binary then cannot happen without killing the broker thread — closing every PTY, orphaning every harness child, dropping every socket. So `spt update apply` degrades to an in-process `Brain::handoff` no-op: the binary swaps on disk but the running daemon keeps executing the old c...\n*327│- **Invariant:** the broker runs as its own long-lived process that survives every brain restart; the brain restarts onto the new binary and re-attaches via the versioned IPC. A routine (brain-only) update must leave every hosted endpoint untouched at the *process* level — not merely re-subscribe a brain within the same process. The evidence for REQ-UPD-3 / REQ-DAEMON-2 must prove process-level survival (a PTY child + a live QUIC conn survive a brain-process restart onto a swapped binary — SPIKE-01/0...\n 328│- **spt-core mapping:** restoration is ADR-0018 (next milestone). The current `int` tags on REQ-DAEMON-2 / REQ-UPD-3 are regression-masked and re-point at restoration; the broker becomes the always-up per-machine anchor (seed-lock + liveness + brain supervisor). Two-process supervision, generation custody, durable-deadline loop timing, broker-cursor-of-record, and readiness-gated auto-rollback all hang off this.\n 329│- **Origin:** unintended spec/impl drift from ADR-0004 (the broker *process* was specced + spiked but built in-process), discovered during the v0.3.2 fleet update verify. Full audit + decisions: `docs/BROKER-BRAIN-SPLIT-RESTORATION.md` (verified) + ADR-0018.\n 330│<!-- [doc->REQ-HAZARD-BROKER-PROCESS-ISOLATION] -->\n    │...\n 356│### 6.11 Brain respawn must exec the APPLIED bytes, not the renamed old binary (Linux `current_exe` follows the apply-rename; readiness ≠ new-bytes) `[REQ-HAZARD-BRAIN-RESPAWN-PATH]`\n*357│- **Failure:** the broker respawns the brain candidate from `std::env::current_exe()` resolved **per spawn** (`brainproc.rs:817`). `spt update apply` swaps the binary by renaming the running file `spt` → `spt.old-N` and writing the new bytes at `spt`. On **Linux**, `current_exe()` = `readlink(/proc/self/exe)` is **inode-tracking** and follows the rename to `.old-N`, so the resident broker respawns the brain onto the **OLD** bytes — the brain comes up ready (readiness passes), the trial **promotes**, ...\n 358│- **Invariant:** the candidate-binary default is the canonical exe path **captured once at broker start** (before any `apply` can rename under the process), never a per-spawn `current_exe()` — giving Linux the path-at-start semantics Windows already had. AND promotion is **bytes-gated**: a trial promotes only if the candidate's stamped `brain.ready` `exe_hash` equals the staged artifact's hash for this platform; a mismatch is a failed trial → auto-rollback + loud notif (readiness alone is not proof t...\n 359│- **spt-core mapping:** v0.4.2 fix (`V042-PLAN.md`). Half 1 = `spawn_brain_supervisor` canonical-exe capture threaded into `spawn_brain_child`'s `None` default; Half 2 = the promotion bytes-gate in `supervise_brain`'s `Promoted` arm (`TrialEnv::ready_exe_hash` + `staged_artifact_hash`). The rollback path (`Some(.old-N)` selection) is unchanged. Sibling of 6.7 — the broker *process* is correct; the bytes it respawned the brain ONTO were not.\n 360│- **Origin:** ADR-0018 Q3 silently assumed `current_exe()` path-string semantics; surfaced by the v0.4.1 fleet-roll `exe_hash` bytes assert (agents `todlando` + `doyle` + `deployah`, 2026-06-11). ADR-0018 Q3 amended.\n    │...\n 423│<!-- [doc->REQ-HAZARD-BROKER-SEED-WIRE-SKEW] -->\n*424│- **Failure:** the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004's no-terminate-during-update pillar forbids auto-killing it — 6.7). A self-update that changes a daemon-state WIRE FORMAT — e.g. the v0.9.0 adapter-agnostic `Seed` (the `adapter` field dropped) — therefore lands a NEW-version CLI talking to the STILL-RESIDENT OLD broker. The old broker cannot deserialize the new `Seed` (its formerly-required `adapter` is absent), so it drops the seed-...\n*425│- **Invariant:** (a) spt-core surfaces an ACTIONABLE diagnostic on the seed-ack EOF — naming the stale-broker cause + the fix (`spt daemon stop`; the broker restarts on the next `spt api` call) — never the bare io error (scoped to `UnexpectedEof` on the seed-ack path, so it never mis-fires on an unrelated error like a refused connect). (b) A daemon-state wire change requires a DELIBERATE full broker restart; this is NOT automatic — ADR-0004 forbids auto-killing the resident broker, and a brain-only...\n 426│- **spt-core mapping:** `startup::seed_fail_message` (the `UnexpectedEof` → actionable-hint branch) fired from `cmd_seed`'s `put_seed` error arm; the broker's seed-control residency (`seedmap::serve_seed_control`, held across brain restarts — 6.7). Unit: `seed_fail_eof_gives_actionable_stale_broker_hint` (EOF → the `spt daemon stop` hint naming the stale broker; a non-EOF kind → the plain message, no mis-fire).\n 427│- **Source:** perri PREP-4 FINDING 1 (v0.9.0 dogfood) → v0.9.1.\n 428│\n    │...\n 499│- **Failure (doyle instrumented RACEDIAG repro, kitsubito):** on a brain-restart re-serve the handoff brain registered as controller on the SAME session TWICE over the SAME socket — `Brain::handoff` eagerly `subscribe(prior.next_seq=1)` → `become_controller(from_seq=1)`, spawning writer-A (writes seq 1); then `serve_attach` re-handled the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, spawning writer-B (writes 0,1). `become_controller` dropped the prior `Co...\n*500│- **Invariant:** on a single brain↔broker connection exactly ONE `controller_writer` is ever the LIVE writer; a SUPERSEDED writer writes no further frames after the epoch bump it observes; and **every `controller_writer` emits a strictly ASCENDING seq stream** (sorted initial batch + ascending live frames). The CORRECTNESS guarantee that falls out: a snap-above consumer over any interleaving of ascending writers — where the surviving writer (`serve_attach`'s `attach_as(sid,0)`) offers the COMPLETE ra...\n 501│- **spt-core mapping:** impl `brain.rs` `Brain::handoff` (KEEP the eager `subscribe`; seed `session_cursors`) + `Brain::subscribe_with` (resume-mode dedup-cursor reset to `from_seq`, shared by `attach`/`attach_as` — the operator-stream boundary fix) + `broker.rs` `OutputLog.controller_epoch: Arc<AtomicU64>` / `become_controller` (atomic `fetch_add`, passes the new epoch + `Arc::clone` into the writer) / `controller_writer` (epoch gate read UNDER the lock on both loops) / `mark_controller_gone` + the `C...\n 502│- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20; design corrected across two gate rounds — fix #1 reverted, then the kitsubito RACEDIAG pinned the residual to the consumer-side operator-stream boundary, fixed by the `subscribe_with` cursor reset on `attach_as` re-subscribe). The last v0.13.0 ship-blocker.\n 503│\n    │...\n 605│<!-- [doc->REQ-HAZARD-BROKER-FLOOR-LOCK-POISON] -->\n*606│- **Failure (paid-for class):** a brain-only self-update restarts the BRAIN but keeps the BROKER process — and every one of its `Mutex`es — ALIVE by design (REQ-UPD-3). So a single panic while another thread held a broker-resident lock POISONS it permanently: the next `.lock().unwrap()` panics too, kills its per-conn reply thread, and EVERY subsequent attach silently deadlines (`spt rc` → \"brain IPC read deadline elapsed\") while non-locked ops keep working. The effect journal (bug #16, `[REQ-HAZARD...\n 607│- **Invariant:** NO broker-resident lock reachable from serve/dispatch may use a bare `.lock().unwrap()`. Recover via the shared `recover` (`into_inner` — safe for the short, coherent-on-recovery map ops of `sessions`/`pair_holds`) or, for the `OutputLog` ring, `recover_log` — which adds a COHERENCE CLAMP on the recovery path: a panic mid-`append` can leave the ring torn (over-cap, a last seq not below `next_seq`, non-monotonic), and serving those bytes risks garbage, so `clamp_or_reset` cheap-checks...\n 608│- **spt-core mapping:** `broker::recover` / `broker::recover_log` / `OutputLog::clamp_or_reset`. unit = a poisoned session-map recovers to a usable guard (the next attach still opens), a torn ring clamps/resets-empty only when torn (coherent untouched, `next_seq` preserved), and `recover_log` wires the clamp onto the poison branch — each RED-first against a bare `.lock().unwrap()` / an un-clamped `into_inner`. int (scripted panic-under-lock during concurrent attach → next attach opens) deferred to th...\n 609│- **Source:** REMOTE-TRUTH B-1 (pivoted — the triage's named floor sites were already closed by `[REQ-HAZARD-INJECT-WORKER-POISON]`; the surviving attach-path lock class is the real root), doyle B-1 ruling (recover all three via one helper + the OutputLog ring coherence clamp).\n## NEXT-MILESTONE-BUG-TRIAGE.md\n 173│`spt rc eel-a` → `RC_FAIL:eel-a: attach request: brain IPC read deadline elapsed`. Worked shortly after creation. **LIVE EVIDENCE (doyle, this box):** endpoint list/daemon show eel-a `ready=true alive=true` (alive is BIND-gated — set at api bind, does NOT reflect live brain health), yet the attach IPC times out → the brain isn't servicing IPC (wedged/dead) while the perch still reads alive. daemon pid 19180. **STRONG LEAD:** the v0.18.0 `spt update apply` daemon/brain RESPAWN (done on this box earl...\n*174│- **Root cause (agent E, conf M-mechanism-H): broker-resident shared-state corruption from the self-update brain-RESPAWN — NOT the brain itself.** The only deadline-bounded call in request_attach is `net_open_stream` (rc.rs:1086, awaits NetStreamOpened under ~10s); error minted brain.rs:1442 = broker's per-conn serve thread sent NO frame for KIND_NET_STREAM_OPEN. DISCRIMINATOR: on the same rc conn, KIND_SESSIONS + KIND_NET_DIAL_LOOPBACK already SUCCEEDED (broker pid 19180 alive, thread reached attach) ...\n 175│\n 176│### G-#17 — no digest for eel-a  · STATUS: SHIPPED CLOSED — W6 env-read @9305d19 (REQ-DIGEST-PROFILE-ENV) + W6b fetcher-strategy @b9e5ce9 (REQ-DIGEST-FETCHER-STRATEGY)\n 177│> **W6** shipped the harness-agnostic `[env] direction=\"read\"` seam (capture-at-bind → info.json.read_env → digest fill_template keys → `value` fallback → `~` expansion → skip-diagnose). **W6b** closed the `{project}` design-gate per doyle's CONTEXT-symmetry ruling: `[digest]` gained a `fetcher` strategy mirroring `[history]`'s — the ADAPTER's extractor locates+reads+emits (no spt-core pre-read, no `{project}` slug), fed only harness-neutral `{session_id}`+`{cwd}`(=info.json.cwd)+captured rea...\n## NEXT-MILESTONE-PICKER-TRIAGE.md\n 124│legs (old running image; F-025's exact dormancy class). Operator updated + bounced the\n*125│daemon; doyle re-verified on-node: `spt 0.21.0`, **`broker image: 0.21.0 (matches\n 126│installed)`** (first field use of the F-025 surface), ball-b info.json clean\n 127│(`controlled:false`). Gossip refreshes on pump cadence. NOT a code defect — closed.\n 128│\n## NEXT-MILESTONE-REMOTETRUTH-TRIAGE.md\n 149│**B-1 — poison-proof the broker floor locks (the wedge itself; top-ranked\n*150│root).** Brain-only update keeps broker + QUIC conns + ControllerSink alive\n 151│by design (REQ-UPD-3); E-#16 fixed journal poison + loopback bound; the\n 152│surviving class is the inject/controller floor Mutexes with bare\n 153│`.lock().unwrap()` — broker.rs:1163 (flush_inject_floor), :1297 (inject\n    │...\n 160│concurrent attach; next attach must still open. KNOWN-HAZARDS entry rider\n*161│(brain-only update + poisoned broker-resident lock strands serve/attach).\n 162│**B-1 PIVOT (doyle ruling, 2026-07-05, build sweep — 4th triage-drift this\n 163│wave):** the 3 named floor sites were ALREADY poison-proofed post-triage\n 164│(`lock_floor()` recover helper, REQ-HAZARD-INJECT-WORKER-POISON, red-first\n 165│covered) — B-1-as-specified is closed. The SURVIVING members of the same\n*166│hazard class (a panic under a broker-resident lock during brain-only-update\n 167│churn permanently wedges serve/attach) are the attach/serve/dispatch-path\n 168│bare `.lock().unwrap()`s: `sessions` Mutex<HashMap> ×18, per-session\n 169│`OutputLog` Mutex ×7, `pair_holds` Mutex ×4. B-1 pivots to poison-proof\n    │...\n 186│(certain): control reap gates on `!has_session` (livehost.rs:749,\n*187│REQ-PRESENCE-CONTROL-REAP-ON-EXIT); brain-only update keeps the session, so a\n 188│SEVERED remote controller's sink is never reaped. Fix: detect a\n 189│ControllerSink whose writer/stream is disconnected → `clear_controller`\n 190│(broker.rs:635 seam + mark_controller_gone :645), boot-tick + detach-detect\n    │...\n 197│**persisted stale stamps**: `controlled`/`driven_by` records that outlive a\n*198│severed controller while the session survives (brain-only update, wedge, or\n 199│an idle-severed QUIC leg), because the livehost reap arm gates on\n 200│`!has_session` and nothing ever re-derives the stamp from broker truth.\n 201│Wire-level severance detection stays with the EXISTING paths (conn EOF,\n## PUMP-W2-DISPATCH-RULING.md\n 39│\n*40│**Design-check verdict: APPROVED.** All 5 rulings honored; premise grounded (D4c `PresenceLog` string-kind forward-compat msg.rs:904, §V4 stagger, ADR-0034 `op_id`/`minter` dedup); the re-pointed IPC-deadline asserts BOTH directions (dead peer never restarts / dead broker STILL does — the check I required). Ready to mint `REQ-PUMP-PEER-ISOLATION` + build POST-W1-gate, in a `.worktrees/` worktree (NOT the shared main checkout).\n 41│\n 42│- **Q1 — round-deadline value:** a **DERIVED CONST** = `W1_final_bound + fixed margin (~2s)`, NOT a config knob (no operator-facing tuning surface for an internal timing detail — a knob is a mis-tune/maintenance liability). Reference W1's bound constant directly; **pin the value when W1 lands its final bound** (genuine W1-before-W2 dependency — don't hardcode 10s now). Margin covers event IPC transit + drain scheduling + slack for the concurrent dials to all resolve (~one bound, not N×bound).\n 43│- **Q2 — semaphore home:** **NetHost-SHARED** (not pump-scoped). It guards the broker's iroh connection resource, which lives at the NetHost, and protects ALL dial callers (pump + shellwake + pairing) from a concurrent-dial storm — a pump-scoped limiter leaves the others unprotected + duplicates the concern. **CAVEAT (load-bearing):** the existing BLOCKING `dial()` MUST acquire the SAME permit too — otherwise the cap only bounds `submit_dial` while `net_dial` piles on unbounded on top, defeating th...\n## THE-FORKENING-TRIAGE.md\n 87│  order). Core already current → adapters only. `--core-only`/`-c` skips the\n*88│  adapters leg. Invoker survives by construction (brain-only cycle) — no\n 89│  re-run machinery.\n 90│- `spt update --restart` = fetch → **adapters** → `apply --finish` LAST\n 91│  (lethal-leg-last, ruled: completes from any invoking context; a finish\n## W6-GATE-VERDICT.md\n 29│- Spot-checked DTO field names against source (3 samples, all match): daemon-status\n*30│  `stall_evict_count/last_ms/pump_heartbeat_ms/broker_stale` (cli.rs:3956-3980),\n 31│  `AdapterVersionJson{adapter,version}` (cli.rs:8641), digest `input_seq`/`after_predates_window`.\n 32│\n 33│### 3. SPT_INJECT_VERIFY_ECHO CC-manifest wiring — CLOSED adapter-side (perri) ✓","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784247488071}}
{"type":"message","id":"a6fe8542","parentId":"35cf06a2","timestamp":"2026-07-17T00:18:13.494Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Identifying dormant broker features in IPC protocol**"},{"type":"toolCall","id":"call_5re8vgcRrwWV8bUbQmfjWYiL|fc_099a7567dadef391016a5974c3cdac819b8c4d84d985d12aa0","name":"irc","arguments":{"i":"Requesting diff confirmation","op":"send","to":"V034DiffRca","message":"Any commit/diff evidence to add? I found W1 ad7fde8→2a110ee→8425357, tag b7b074c, release commit eb9fd864, plus mixed old-broker gap and --restart/30s reconnect mismatch.","replyTo":null,"await":true,"from":null,"timeoutMs":1000,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":26403,"output":209,"cacheRead":203264,"cacheWrite":0,"totalTokens":229876,"reasoningTokens":94,"cost":{"input":0.13201500000000002,"output":0.00627,"cacheRead":0.101632,"cacheWrite":0,"total":0.23991700000000002}},"stopReason":"toolUse","timestamp":1784247488087,"responseId":"resp_099a7567dadef391016a5974c03e88819b8c2a4b767aef3bd8","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a5974c19424819b87871430dcb31571","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXTDwWreChRSiknSKvwWBpAHtky6sUX-rUj4nedHlJnPmLnAZ_DMjOBdkmge4vUHFiV5IiZORA0w9XSJ_aooSryOivXCy-c_vm9QKAqh7BGOOKSCFCmb1OcP4ZaPAEgqmOFi3cQQ_iL-G54KosdQzp3tbzsPeJdNtRko_yZCxovk3LOcZTNVFpv2pdrFvbH_sKS1BmsRcj2ySnmwNPXJFbS8Yai0Bi1_yfIh1FTsP6U5ClsAhejlzEDHQE1xekwZAhJWWx6vNXYNqFOgaplF5DsU-BrigpA7oS0hFeYG_95YA8lxLq5UCHZkYDm83VBbY_sJLSCJLb7Lmn-hXFhk3Lhkcazo4D1nZLtBdHmNbI0wshf4czUIFR5DizRc7fRUiQS6Be9K8R8UnN85K3pCB0lLYnKWzlyADaUz8ZMBKr5tUR4xHMS7gP_tziYT7nQgbDVYAMESgJJpzmn1oK9DAK2KXZkYmqicQ6rZk_gHj7aRZ3I9meZc6SSfS3JC4P6Ejs8LCjQv-3hXHPr28ckvIvi_AZHGrDvClZQohqfv5PM95tU-uBdOeDFTPZLdCNGQXlkUoh8aTD2AasU5mI5H0jMMes3qWPxTdY7rW3YNZIrJLELmKAXU54OW5gJamhlChyueimAjN2QCCOpcaABQs4hmR_RtMA-IPFp_LmR1uY2a8TGHZXylO6P8t2YLbPlSocvKzirryuWPUwzkyVTrnkiEYjCgFdvIfmm20nuUEc9jZE3PxbT3W5ODK7-ugdzUx0rHeo6WNu8T26QlpCkzX6QUl89Zb4tUTs4Y0vthp2rcNxjp-xHUxYv8X2Sq3H5pE8oUVkuf-ermeYXqyPXBTG9dVcb-GHt7vpfxlF6WCOqAwtICD64vB9CAUVvYTtASXuapEkUGUgTzrh-XGcJjXO2UbMRg3SjJcRbUCeaz5c15ZUXbEe1NDOtcny-oaLNulZdeC0f9c429Ig1HM1eiPIBLYzf8Q1hLnt8j0pxbboeQBepRHrC4R91CPjUmn6pP9T5KZ1I_nvvJpC-FWOOGcWS9DkMpKQZzY6r5y2OzZI6eL4RA8YgNi7Y3Zp4Fj4lrAtr5WtzlBnr5iroO2KYfU0QXty--qKuN9WRk1KQfI8PE1yHUrkSfE-SHuyGK_r3t91ot6L3ei90AP_1tDE3t3aXZrTq-aF94uYPFH5JaHpWgnH2qv1p1WTSz8aUWn8oLFFrYUHUEdVYi4h-Oa7COuRBe3A-CAFmOOEwFHwDoeNPdwkPSiSVLCjFYrX_DKXkbifCGzoMkAUxnxQOCQgntlZPSJ2HkfqyvBPAPsj2_3gxUrhAd82ODt6dpudt1fsptuCHLWd59dO2kVFfhizEzxzFehUf05YbFX6vSAaZQauanfTr50cIkHVo_88L4YnuRPfpnTPc2WOfrHlDbG7ZMJzIKXnBrjfzgh8wbT-LgyFssrh19ClcySCi_cX8iTGgizEw-JexcWEVpAFyM_SOFs8gK_ZmlEiVUhWvmosrxqvd2I76sEJOxs8Ask3KgFhNeermz","summary":[{"type":"summary_text","text":"**Identifying dormant broker features in IPC protocol**"}]},{"id":"fc_099a7567dadef391016a5974c3cdac819b8c4d84d985d12aa0","type":"function_call","status":"completed","arguments":"{\"i\":\"Requesting diff confirmation\",\"op\":\"send\",\"to\":\"V034DiffRca\",\"message\":\"Any commit/diff evidence to add? I found W1 ad7fde8→2a110ee→8425357, tag b7b074c, release commit eb9fd864, plus mixed old-broker gap and --restart/30s reconnect mismatch.\",\"replyTo\":null,\"await\":true,\"from\":null,\"timeoutMs\":1000,\"peek\":null}","call_id":"call_5re8vgcRrwWV8bUbQmfjWYiL","name":"irc"}]},"duration":5387.664099998772,"ttft":1485.0346000045538,"contextSnapshot":{"promptTokens":229667,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_5re8vgcRrwWV8bUbQmfjWYiL|fc_099a7567dadef391016a5974c3cdac819b8c4d84d985d12aa0","toolName":"irc","startedAt":"2026-07-17T00:18:13.494Z","intent":"Requesting diff confirmation"},"id":"976e4dc3","parentId":"a6fe8542","timestamp":"2026-07-17T00:18:13.494Z"}
{"type":"message","id":"b0ad6157","parentId":"976e4dc3","timestamp":"2026-07-17T00:18:14.499Z","message":{"role":"toolResult","toolCallId":"call_5re8vgcRrwWV8bUbQmfjWYiL|fc_099a7567dadef391016a5974c3cdac819b8c4d84d985d12aa0","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- V034DiffRca: injected\n\nNo reply from V034DiffRca within 1.0s. They may answer later — check `inbox` or `wait` again."}],"details":{"op":"send","from":"V034ReleaseRca","to":"V034DiffRca","receipts":[{"to":"V034DiffRca","outcome":"injected"}],"waited":null},"isError":false,"timestamp":1784247494498}}
{"type":"message","id":"d06990e9","parentId":"b0ad6157","timestamp":"2026-07-17T00:18:22.985Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Formulating source-grounded fault report schema**\n\n**Analyzing timing and retry discrepancies**"},{"type":"toolCall","id":"call_uif9UcZVGlBCQyBLqSpdNdPN|fc_099a7567dadef391016a5974ce78ec819b956f2662571c91d8","name":"grep","arguments":{"i":"Locating flood session count","pattern":"const N|n_sessions|heartbeat_ticks_under_flood","path":"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","selector":"175-260","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":1070,"output":345,"cacheRead":228864,"cacheWrite":0,"totalTokens":230279,"reasoningTokens":271,"cost":{"input":0.005350000000000001,"output":0.01035,"cacheRead":0.11443199999999999,"cacheWrite":0,"total":0.130132}},"stopReason":"toolUse","timestamp":1784247494546,"responseId":"resp_099a7567dadef391016a5974c6c0f4819bb03501c8a1952461","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a5974c7d12c819bb77bf9abefeb7591","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXTOYY0DdQjofWTw38ZC5ch-eO1c6Hb85z4BpFz8PVwHFq9zZZYjTljtkbrpsIvCn0ZZg9EohYHkm3Qvsbepww6erS_NVpUDMFHh1BIn41Aqbd9j_t9F8Ro2NBGju9FWxBkFK9UhBRLUhLWaZyXNIoJ-qxORHNfTyuYgAQP5Ko_1X7ASpqfmwO2CRg7CH6mRiJVx2vrvJ6xvyPjfyEP6jIHsQejWQzAi1iNDmfdmFxLh6RPACMHEizoE6MVp1gNoftNSbwu3dfRdCn0RhFNG729VVisQ4qqlbPRwP4MOMNdlQC5DGKlNhWYA4sdxoubC9TePiLysnz9S38OvLtSddMAq4mV6rXXgsVlXqGiiNmHikPv3U3qXd7qZGrG4yhjRWO2MLBpiIh4wFfbH92FgOZsHgcsjUeQMLqCwCUiUDvb1NZajHe4_D_eOCpv48gbDQGYRTuL_PxlPgY0nHc_4OGMxHDLQH6N_CwULcxBjmdnM_che_o4zxbLTZFDByk8d4YQGwhfB4geNbfgWkjjPmz0AnXcbBvERD4OPLrkZP7zznSpgIoY03FkwaVm7OFgiQOO9aANfgf9N4YqmzWhRmu_zzQkp-9HR46__y2i-eITjRb0m9MDVfR2Lf5HWQToPFVJdU30BcrStE4gonorZ52gKWUXy43lNJGB-UWix3mSVXElFrpYsSRkDCkLW6htcfuEKzl-S8KhSvWaunwj4XcV3Wl64eODEQqA2VSdOleMMK7Jgp5Bepc_Tq5BOskzmdfPHWi4mweiOw9cXJEso5V6APDBf9Td_be1hXzhc2i_9dioKm2d6EPhObBZNVHq0piYOeohAeSasY1I7y1qSECqcHOpYBW2-yT3xwoRGMAleGWRSUBM0YIjYjlLctTlCRm1nXyMvcbbKX32MrtO4ZsL5KSA4kYC7pABjQBToR0tkzWT6AniFCfyBt8g3VyuXcfUxAaEUSKvI9vhXreOcDQ_UFpIHbV6rw0SlqTh7r_sIB_y3T1wIQPPVL8RorjqX23nYcP_CnQGxCL-2dhfzfHbmYD5klGLvwI800Bm1SpzyL353tysrQF6wwtb0H2liOx_ZI_JQ892qjE8HePYl_rXAOMDKjcej6EWEJS4oeAhQ7QXmuBzNkaFI3EalzDRwi3xQyhEnYz_YP9sXgDQIdPrKPj7-0pfa_8KOBfFfCg4YX4wEKEb6WPwU7BTACDidiV0ulgDrCWVGPYgMobZ3rolRC4XqndTVvHK3P2CDgUDY5XONVtvq-qYIB2cdw7Tq64G7DwH-k2ehk_1jcmnHbeBP81IUWExUowZ30S1FYKFKmgDM7-2rw_9FwDpVxJ-MDSC4c7f5uB8RGE1Ijil4W3mRx2YB8wurc3Qk46QUOb2XQEUu-g4U7qzUh8f95Vgpe6CrLeG8txA6WK6YP0oMavgWZnWzaqwOUXq8jgTzMTu6Sk3ua1zQoelkBHFR_z6EyMQ_m94JDcGW4vfDpr4meItYmr0zfPh1pGZoo9GT4msZrQmGsSTLecdT7hczes35Cu2x3jMdGYm0dc8zDXLeS5tPfNIoIeg59Mmt89zJWdWBAygk2v1YSlHEtGLNK74vzsOjUJq1ivPryWj8CbgxYLGPT3U0Pcq6jkluV2rFakg_e7DMkmc10SxR5JQmi0ZctM7SE6uYHn69TzwA36LWBjUWkduJYsYIHCVHXrebD4x22hCeWA43rNcPnW1DSaG38_MVI-Ek3eUI7gGMldnwm-nD8KDazBSmmqWU6_L62on7SgRvzAytclq-1YeajlNL562sLdMiir5G3mFGIme5A8SZ2btpt-yMWVeqcPjucJhlNWw_wadmhFuDUGMtC486sHLVOeKliRQAFIToPkgd2fMhQDcNVaMQNwxaxIufUGgVtP1WUOfMyf0N4pwYGx1xaj9DAPLOrHtKubsFxIsWdEzahraOPosu_iEOEyUiDK5ER0MKloHvIXyuLeuHe3rDqWGle3mAld7DURyq9u0oc-FgSeAKqwkDUDP79Tml7oQ2-XvPgARfXzTNLqoH8EEKSvDG4_cpSNdy52F8fixOtm9PoeYBYyIiBKir_kCU1XHCy0lQi93Vj4E-i2Eq593ZJVq9oYTqQPvHKMbUQnCNirsUXFN-_lrb7UBVJbGGN7SV_87fjYrtvIkJixZbTmKoRUztTWFZZXNaSECFchs7T6wQ-VTlo4jmi90yZ1BVi48TANGWz8z9b495fdWnVJBgAIecfuPjBtZbvW1Jmq6gjADdoD3afmExYT44KdK1ja4ZA13WI9bFfyyMKz3i4gljUI9sCWCoG7VZnISYCy70yLwLOG1exkIrCQOrulojYLcaKxSsQ4th5T58zS3tPCvXbB0r2YyCOoYelyVBhqKRPrZKhT4cBc4h3rssrrNOUZIOKlhgDvCXVaxivPTYh5fcTI1bmhfGGlkeeAvaS-HwjDZx7LeDZzhEAyM5nRtHgLCqXrwTYWqvXUYtxEayGbI6CGEL","summary":[{"type":"summary_text","text":"**Formulating source-grounded fault report schema**"},{"type":"summary_text","text":"**Analyzing timing and retry discrepancies**"}]},{"id":"fc_099a7567dadef391016a5974ce78ec819b956f2662571c91d8","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating flood session count\",\"pattern\":\"const N|n_sessions|heartbeat_ticks_under_flood\",\"path\":\"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs\",\"selector\":\"175-260\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_uif9UcZVGlBCQyBLqSpdNdPN","name":"grep"}]},"duration":8432.25380000472,"ttft":1261.554200001061,"contextSnapshot":{"promptTokens":229934,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_uif9UcZVGlBCQyBLqSpdNdPN|fc_099a7567dadef391016a5974ce78ec819b956f2662571c91d8","toolName":"grep","startedAt":"2026-07-17T00:18:22.985Z","args":{"path":"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs"},"intent":"Locating flood session count"},"id":"3c46679a","parentId":"d06990e9","timestamp":"2026-07-17T00:18:22.985Z"}
{"type":"message","id":"e2beb42d","parentId":"3c46679a","timestamp":"2026-07-17T00:18:22.992Z","message":{"role":"toolResult","toolCallId":"call_uif9UcZVGlBCQyBLqSpdNdPN|fc_099a7567dadef391016a5974ce78ec819b956f2662571c91d8","toolName":"grep","content":[{"type":"text","text":" 180|/// N flood PTY sessions are already streaming before the probe starts.\n*181|fn heartbeat_ticks_under_flood(mode: ResumeMode, n_sessions: usize, window: Duration) -> u32 {\n 182|    init_home();\n 183|    let name = unique_name();\n 184|    let broker = Broker::bind(&name).expect(\"bind broker\");\n...\n 192|    let mut pids = Vec::new();\n*193|    for i in 0..n_sessions {\n 194|        let (_sid, pid, conn) = spawn_flood(&name, &format!(\"flood-ep-{i}\"));\n 195|        pids.push(pid);\n 196|        conns.push(conn); // hold the spawner controller conns open (undrained)\n...\n 223|fn daemon_cursor_only_resume_keeps_heartbeat_live_steady_state() {\n*224|    const N: usize = 6;\n 225|    let window = Duration::from_secs(6);\n*226|    let ticks = heartbeat_ticks_under_flood(ResumeMode::CursorOnly, N, window);\n 227|    // Healthy heartbeat is ~ms per tick → hundreds per window; the RED path lands ~0.\n 228|    assert!(\n 229|        ticks >= 100,\n...\n 242|fn daemon_cursor_only_resume_keeps_heartbeat_live_respawn_interleave() {\n*243|    const N: usize = 6;\n 244|    let window = Duration::from_secs(6);\n*245|    let ticks = heartbeat_ticks_under_flood(ResumeMode::CursorOnly, N, window);\n 246|    assert!(\n 247|        ticks >= 100,\n 248|        \"a respawning brain's cursor-only resume must keep its heartbeat live while N={N} \\"}],"details":{"scopePath":"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":6,"fileCount":1,"files":["crates/spt-daemon/tests/brain_resume_conn_deadlock.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","count":6}],"truncated":false,"displayContent":" 180│/// N flood PTY sessions are already streaming before the probe starts.\n*181│fn heartbeat_ticks_under_flood(mode: ResumeMode, n_sessions: usize, window: Duration) -> u32 {\n 182│    init_home();\n 183│    let name = unique_name();\n 184│    let broker = Broker::bind(&name).expect(\"bind broker\");\n    │...\n 192│    let mut pids = Vec::new();\n*193│    for i in 0..n_sessions {\n 194│        let (_sid, pid, conn) = spawn_flood(&name, &format!(\"flood-ep-{i}\"));\n 195│        pids.push(pid);\n 196│        conns.push(conn); // hold the spawner controller conns open (undrained)\n    │...\n 223│fn daemon_cursor_only_resume_keeps_heartbeat_live_steady_state() {\n*224│    const N: usize = 6;\n 225│    let window = Duration::from_secs(6);\n*226│    let ticks = heartbeat_ticks_under_flood(ResumeMode::CursorOnly, N, window);\n 227│    // Healthy heartbeat is ~ms per tick → hundreds per window; the RED path lands ~0.\n 228│    assert!(\n 229│        ticks >= 100,\n    │...\n 242│fn daemon_cursor_only_resume_keeps_heartbeat_live_respawn_interleave() {\n*243│    const N: usize = 6;\n 244│    let window = Duration::from_secs(6);\n*245│    let ticks = heartbeat_ticks_under_flood(ResumeMode::CursorOnly, N, window);\n 246│    assert!(\n 247│        ticks >= 100,\n 248│        \"a respawning brain's cursor-only resume must keep its heartbeat live while N={N} \\"},"isError":false,"timestamp":1784247502992}}
{"type":"message","id":"2811b29f","parentId":"e2beb42d","timestamp":"2026-07-17T00:18:28.879Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Identifying memory allocation regression in v34**\n\n**Planning source location metadata inclusion**"},{"type":"toolCall","id":"call_BUHnTF8MBbW2rSKhkmYXgKkL|fc_099a7567dadef391016a5974d49ea8819bb846b267b8c9cd8e","name":"irc","arguments":{"i":"Checking peer findings","op":"inbox","to":null,"message":null,"replyTo":null,"await":null,"from":null,"timeoutMs":null,"peek":false}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":1809,"output":232,"cacheRead":228864,"cacheWrite":0,"totalTokens":230905,"reasoningTokens":175,"cost":{"input":0.009045000000000001,"output":0.00696,"cacheRead":0.11443199999999999,"cacheWrite":0,"total":0.130437}},"stopReason":"toolUse","timestamp":1784247503002,"responseId":"resp_099a7567dadef391016a5974cf4c24819b97e8d75857e6152d","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a5974d0a7d8819b9fa23b9919fbf80a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXTUU5c-cItlLdeGBHnJpqftNtU2bu8AmghmzFs1pFXjRmG6ExWfuZRHj9vIsGg_uM7U2ndfC0TlAWDggICjBRBwFExkMNM4QtultH_9pxe-0L_kMfpyDLRKNEcpwjP8K3ldtm8-5oDHxerrbzhDTDNmUCrOg3PUJh0kpPmFwbrpxBQ4Oej4RIbAszeB4_nRgin7jwfiirnJLrcG2_wZksEoWkGxlddF-7_BmU8i0YQ99Zov3Ae-x4APkLOeyAZXxZCj3CDtPt09B3bRUqk1asFU4znHiHdLfyJuCGH98UA3nA9DDNDltBZlci96m3YotVcwsPFVGprjChUj-hagx2mNedWgoDZJgXOwDID5aql7_4SRGx8haBa8z8K7sz6JR4Sa1fQNQrmiAQQjFD-7-png3aku0H1xZDeNdIUKrv7BK55etH2lYQCymVlsNfcTX2TO9e8BWIAwrhky6mfmXuMGlGs_XuaD1ImuAx6XO6Wcg23EqplSvrHBolrAkkANyXLq3FNHkCTUXxwHnMU3pnY-ui60Wy7nDntDTz6NNPnECUEujTmlAI240Dkw8eNLmS2sxt7ml8Q_Bc8Ryw7TsuLj4hC5qQc1U0ccith_SCkd7vDbbZriyp_9ximiiCr_O6lSkeupk2CWDiNnhZjMtquLJOV_QmJtudF_on5A-B4tIJSPHa90bK1EkTHjfhKQbi_XzeuRCIJ2WC2uRLMJPRGN9EvuaNhs62kVib3JfXl9zm-3Y9AVLSj4rARjrzVjxxLSTDJ1RezbNBB1M07jxKclrAfInmmLhADKGGVF22RGqBfEf8xBTcOlzuunJvOB03Sh847nXUnYe4ZyJmfJxSUDMzT1VkbcIaBzxKafWTgMrzpjOcUe0wZxNLUoe8S8VuR3qci9zSZxS7KK1gnQvHyxVv3w41Vmy1ZD775zY0iJrGpoR2QLPmMIP2dHvvJAF1_2XUGeQ75LiXvaPdkf_PZ5zqc7UuUolGERJwZyCoI4c83UmwscSDIKl0sX4cmFQlGq2pAdi6PJ-qTTgIl65NxZEb9wuadhuBu1t2w4RIuyI9F2zurcd94MQzdzO-dkB2kVayKklExfM06EKQgIBmu9AB-_VepqtktM3de2nGz2gUhGuQoFYNK-r0YO5nGh0D4Sx4UuZj2N-8oQGPFYcXwT1MVZym7hoO1_lDMo0IA4dXriuEDj4f1bQpoll8amIHaOGl0D-RF5wUneBC_rkm42RfGw1W5hHoTWXw8iMF2cH_dpuemrGofJWWqozR469omHbIJWksKPMVIa1aIJ-NxSVewEhAEPiMHqN0cOLuaOjLkn2oLPR5jbagKAukTbg86o7Bd8Ggo9FoCYgEP8JTioIia55MBw8HIYWz14KEpfz8T6bCBlvzQYxj8Mu9Icqv6trKh-PjAN5RiEqXjBtVu2zbq33vnLhw2XGKsdsnaE55hL9LoE0kqArrRC-sHF73McgbQV6xj9K4mJSETIfliQ6eG37-goyqO_ExHmlDhQBM_oHtyIhYK5kwJ132YNzMqrMX-6eZBqAcExY5T6E7uuIqmdJFp4ekO4lo0J7kvRDnuLGLNzaLpEsKyyOyHF0T6_NchwzzllYaLGhmwG6l0Ts5VvS4P5QbFc1hOX3tW9NR3a9z3X7GauR1Tg5eJqNFQfd8r5E5CecbS88yWWyanEbdyT-iTkl9ihpLMJQiwy94j2p-1FvL2ucP4ksVv3O2F4g6QaUuEF9J5v1cZrV81Hn97A8vJKoG0Etb0n7VgoSipMKD2ot2oTmWONqkCz3DzTMQq0X2sn2aTkFpJikvpTFy3_MAzIJAnJtr_Trp7Q_qgbp1wDxHabzXsiFaQXiiprKhlpiy-1gwJ_RUUP88_0yR4rTqWU2Bt74jmxrKx1SdLOMkkBtkl_IttA7d8mmG9_59jWRQFrRoIfWIKH4_FME_WkPqCdggXeYejvQQV6w1s5udIY3PwDHt6KlooAoiH0c_I9SNF8hJoKYufYp9vzZ9BphjlCt-y59gkxtwj1d8ElT8GmMJahFIwFz-ZtqGfUVY7lJe6WLuFrJvRXXq2JBanUZPKBlty6im6eJvAMIOzS0KoOVY_edkEFzsAAXZXd","summary":[{"type":"summary_text","text":"**Identifying memory allocation regression in v34**"},{"type":"summary_text","text":"**Planning source location metadata inclusion**"}]},{"id":"fc_099a7567dadef391016a5974d49ea8819bb846b267b8c9cd8e","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking peer findings\",\"op\":\"inbox\",\"to\":null,\"message\":null,\"replyTo\":null,\"await\":null,\"from\":null,\"timeoutMs\":null,\"peek\":false}","call_id":"call_BUHnTF8MBbW2rSKhkmYXgKkL","name":"irc"}]},"duration":5876.840800002217,"ttft":1648.174099996686,"contextSnapshot":{"promptTokens":230673,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_BUHnTF8MBbW2rSKhkmYXgKkL|fc_099a7567dadef391016a5974d49ea8819bb846b267b8c9cd8e","toolName":"irc","startedAt":"2026-07-17T00:18:28.879Z","intent":"Checking peer findings"},"id":"241c9a52","parentId":"2811b29f","timestamp":"2026-07-17T00:18:28.879Z"}
{"type":"message","id":"17df37fd","parentId":"241c9a52","timestamp":"2026-07-17T00:18:28.880Z","message":{"role":"toolResult","toolCallId":"call_BUHnTF8MBbW2rSKhkmYXgKkL|fc_099a7567dadef391016a5974d49ea8819bb846b267b8c9cd8e","toolName":"irc","content":[{"type":"text","text":"1 message(s):\n- [1532a966e41c5f18] V034DiffRca (reply to 1532a95cdd5c5f16): Confirmed the same history: v0.33 tag object `21a46d5` / release commit `42d06a8`; W1 commits `ad7fde8` (retire + durable opener + retry claims), `2a110ee` (finished Attach terminal; request/reply FIN remains servable), `8425357` (retirement visibility-only, no teardown), docs `571d6a5`; v0.34 release commit `eb9fd864`, tag object `b7b074c`. Source diff is concentrated in `dispatch.rs`, `nethost.rs`, `brain.rs`, `broker.rs`, `msg.rs`, tests/fixture/docs.\n\nStrong warning: W1 adds no 20–30s timer—retry is 500ms then 1s, three attempts; poll is 100ms. Strongest diff-local stall seam is IPC amplification during rediscovery: each stream now does opener RPC and completed workers open another broker conn for retirement; transient opener error falls back to old subscribe/replay. It predicts startup/brain-cycle burst and attach/net-dispatch scope, not periodic ConPTY-wide 20–30s freezes. The existing 15s per-physical-connection writer deadline and 30s RC reconnect/first-event windows fit timing but predate the tag delta. Mixed v0.34 brain + old broker leaves retirement unavailable until broker restart and therefore does not fully deliver the release claim; worth emphasizing alongside your `--restart` mismatch."}],"details":{"op":"inbox","from":"V034ReleaseRca","inbox":[{"id":"1532a966e41c5f18","from":"V034DiffRca","to":"V034ReleaseRca","body":"Confirmed the same history: v0.33 tag object `21a46d5` / release commit `42d06a8`; W1 commits `ad7fde8` (retire + durable opener + retry claims), `2a110ee` (finished Attach terminal; request/reply FIN remains servable), `8425357` (retirement visibility-only, no teardown), docs `571d6a5`; v0.34 release commit `eb9fd864`, tag object `b7b074c`. Source diff is concentrated in `dispatch.rs`, `nethost.rs`, `brain.rs`, `broker.rs`, `msg.rs`, tests/fixture/docs.\n\nStrong warning: W1 adds no 20–30s timer—retry is 500ms then 1s, three attempts; poll is 100ms. Strongest diff-local stall seam is IPC amplification during rediscovery: each stream now does opener RPC and completed workers open another broker conn for retirement; transient opener error falls back to old subscribe/replay. It predicts startup/brain-cycle burst and attach/net-dispatch scope, not periodic ConPTY-wide 20–30s freezes. The existing 15s per-physical-connection writer deadline and 30s RC reconnect/first-event windows fit timing but predate the tag delta. Mixed v0.34 brain + old broker leaves retirement unavailable until broker restart and therefore does not fully deliver the release claim; worth emphasizing alongside your `--restart` mismatch.","ts":1784247503760,"replyTo":"1532a95cdd5c5f16"}]},"isError":false,"timestamp":1784247508879}}
{"type":"message","id":"6fd627d9","parentId":"17df37fd","timestamp":"2026-07-17T00:18:31.919Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Checking production invocation timing**"},{"type":"toolCall","id":"call_YPkFF74UPHpSP9uPTNtiEDHX|fc_099a7567dadef391016a5974d784fc819b8ba0c17a2ec9a28b","name":"grep","arguments":{"i":"Locating dispatcher poll cadence","pattern":"run_dispatch_loop\\(","path":"crates/spt-daemon/src; crates/spt-daemon/tests/fixtures","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":1373,"output":96,"cacheRead":229888,"cacheWrite":0,"totalTokens":231357,"reasoningTokens":29,"cost":{"input":0.0068650000000000004,"output":0.00288,"cacheRead":0.11494399999999999,"cacheWrite":0,"total":0.124689}},"stopReason":"toolUse","timestamp":1784247508895,"responseId":"resp_099a7567dadef391016a5974d508b4819ba064a5106c3495ad","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_099a7567dadef391016a5974d64000819b9f20d0da8f583400","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXTXW966yhx-9agd-e6jCVVZzXfFdfj_-jfhs9KBsTn0t4JF1je1OgN4Dmy43PCeOw-1IL4G37J8I80ue3FIFarAA5vx4LNnUT9TULmTiE8gI0Dj_rd8o7orluVrGrwvxglDyQCx1j3dye-rjWg48LAKWtyrAv0jpqnkxjrCs0QQqivX8btkOxGttRFrnsSImJGKINo8zPfK-JXtWFE-EwezbU030Jy_zjO__6Y2EfCQjEMv8eUQmglMPJ9ieqZVcf7o2b53o7E60OROcG7_Tu27iE_p_yjj-uA5Rulg75gOrC-mk0McjZKfSzB-Gv3PLMI1YSEoIeHmAIRD4nxqO36cV4djxv1yo8l_5mIU54D3o3FATUFhbBNCX6A1eQOLcAd-iLxgADB2c5LB7Sl0oG_epUcmCZ-b4Zejh_S7m9WdvtMLgED-nMitpyJZQhGPrBfLRUvZnkaBWdyzbucPC25o6avKFlK5H5cTO5grILHoZV-s2LAE0eWYVM07gD-fjWj1KvEQbqE2P_7yPKu4T9J_g7StZssn9U_9qzBmS5nGKIssGPxEWA6UPkezuQy43Uf40t7BNTM3260a-_VoERYnGoJkLz84HQKOAwRoBctSFphHh9WS4-Y6Iw9kj5PNbfkNt4X6kpHtvbvchXZsNBPFF1fj7iYyf0uNDwtvmQayVwGZn7B_Ks7hSaw2ikVU97j9uazOj_zR6Kwwe8ZGsNclsV4kN1myg1XDoovq322RvYLxJPJ5NwAEYperWo416HCYuydnkRFWS4H-UVg2NFau6kv860gWbHMlokPw0LtZOyVrl2zm_5K7_eC0c1LqCanhe7J_t8jcm_EPEVlE-HdWLhf8Y5iawEWZ8WTjowTrhzV3SWcz4DKphVUr0j1BY6Na0C_4wPZNMHf52n-8z4agbfQtGqI8sTbXdZoOxXpiMKTbYLhUuJdW4lpAwR3O-lHsw0GusNDjaRC8IMxcs1SnbYTzFucmLuo38kOLfPZBvOnrOeOm5O4ZpDXhN10rx3M2PEQM_1QKY6qGSsNvBajRHM9QrcNJ7iRex0X69zqeOMg3JFDEFQvVOaX7yMXnfKJm14huZrjG4X4aBMPeCeJIkQ==","summary":[{"type":"summary_text","text":"**Checking production invocation timing**"}]},{"id":"fc_099a7567dadef391016a5974d784fc819b8ba0c17a2ec9a28b","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating dispatcher poll cadence\",\"pattern\":\"run_dispatch_loop\\\\(\",\"path\":\"crates/spt-daemon/src; crates/spt-daemon/tests/fixtures\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_YPkFF74UPHpSP9uPTNtiEDHX","name":"grep"}]},"duration":3005.695900000632,"ttft":1346.1796000003815,"contextSnapshot":{"promptTokens":231261,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_YPkFF74UPHpSP9uPTNtiEDHX|fc_099a7567dadef391016a5974d784fc819b8ba0c17a2ec9a28b","toolName":"grep","startedAt":"2026-07-17T00:18:31.919Z","args":{"path":"crates/spt-daemon/src; crates/spt-daemon/tests/fixtures"},"intent":"Locating dispatcher poll cadence"},"id":"55a17be7","parentId":"6fd627d9","timestamp":"2026-07-17T00:18:31.919Z"}
{"type":"message","id":"42870992","parentId":"55a17be7","timestamp":"2026-07-17T00:18:31.934Z","message":{"role":"toolResult","toolCallId":"call_YPkFF74UPHpSP9uPTNtiEDHX|fc_099a7567dadef391016a5974d784fc819b8ba0c17a2ec9a28b","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/\n\n## src/\n### dispatch.rs\n 298|/// stream).\n*299|pub fn run_dispatch_loop(\n 300|    broker_name: &str,\n 301|    paths: &DispatchPaths,\n 302|    registry: Arc<RegistryHost>,\n...\n 894|        if let Err(e) =\n*895|            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 896|        {\n 897|            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 898|        }\n\n## tests/fixtures/\n### dispatch_fixture.rs\n 26|    let stop = AtomicBool::new(false); // runs until killed — the test owns the lifetime\n*27|    if let Err(e) = spt_daemon::dispatch::run_dispatch_loop(\n 28|        &broker,\n 29|        &paths,\n 30|        registry,"}],"details":{"scopePath":"crates/spt-daemon/src, crates/spt-daemon/tests/fixtures","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":3,"fileCount":2,"files":["crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/tests/fixtures/dispatch_fixture.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":2},{"path":"crates/spt-daemon/tests/fixtures/dispatch_fixture.rs","count":1}],"truncated":false,"displayContent":"# crates/spt-daemon/\n\n## src/\n### dispatch.rs\n 298│/// stream).\n*299│pub fn run_dispatch_loop(\n 300│    broker_name: &str,\n 301│    paths: &DispatchPaths,\n 302│    registry: Arc<RegistryHost>,\n    │...\n 894│        if let Err(e) =\n*895│            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 896│        {\n 897│            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 898│        }\n\n## tests/fixtures/\n### dispatch_fixture.rs\n 26│    let stop = AtomicBool::new(false); // runs until killed — the test owns the lifetime\n*27│    if let Err(e) = spt_daemon::dispatch::run_dispatch_loop(\n 28│        &broker,\n 29│        &paths,\n 30│        registry,"},"isError":false,"timestamp":1784247511933}}
{"type":"compaction","id":"5dab698f","parentId":"42870992","timestamp":"2026-07-17T00:18:32.045Z","summary":"You are resuming a prior conversation. Its earlier turns were archived to reclaim context and are reproduced under HISTORY below, oldest to newest. Read HISTORY in full, then continue from the live conversation that follows it.\n\nThe archived transcript is compact: each turn opens with a heading — `# User ¶`, `# Assistant ¶`, or `# Tool call ¶` — assistant reasoning is wrapped in _italics_, and tool output sits inside `<out>…</out>`.\n\nReading HISTORY:\n- Plain-text sections are the verbatim transcript — rely on them exactly.\n- Some middle sections are attached as images instead of text. Each image is a page of that same transcript and belongs at its place in the reading order, between marked delimiters. Within an image, a solid black cell marks a newline and runs of spaces collapse to one.\n  - A frame is one grid 196 characters wide and up to 71 rows tall: read left to right, top to bottom — there is no word wrap, so a word may break across rows.\n  - Text inside `<out>` is dim gray — that gray is archived tool output, not conversation.\n- When an exact earlier detail matters and a section reads unclearly, re-derive it from the workspace (re-read files, re-run commands) rather than guessing.\n\nFILES\n===================\nCHANGELOG.md (Read)\nCONTEXT.md (Read)\nCargo.toml (Read)\n# .git/\npacked-refs (Read)\n## logs/\nHEAD (Read)\n### refs/heads/\nmain (Read)\n#### release/\nv0.34.0 (Read)\n## refs/tags/\nv0.33.0 (Read)\nv0.34.0 (Read)\n# .github/workflows/\nrelease.yml (Read)\n# crates/\n## spt-daemon/\n### src/\napplyhost.rs (Read)\nbrain.rs (Read)\ndispatch.rs (Read)\nnethost.rs (Read)\nupdate.rs (Read)\n### tests/\nendpoint_survival.rs (Read)\nredispatch.rs (Read)\n## spt/\n### src/\ncli.rs (Read)\nrc.rs (Read)\n### tests/\nbrain_survive.rs (Read)\n[…7 files elided…]\n\nHISTORY\n===================","shortSummary":"Archived 100,348 chars of history onto 6 snapcompact frames (+27,833 chars as text)","firstKeptEntryId":"a6fe8542","tokensBefore":231357,"details":{"readFiles":[".git/logs/HEAD",".git/logs/refs/heads/main",".git/logs/refs/heads/release/v0.34.0",".git/packed-refs",".git/refs/tags/v0.33.0",".git/refs/tags/v0.34.0",".github/workflows/release.yml","CHANGELOG.md","CONTEXT.md","Cargo.toml","crates/spt-daemon/src/applyhost.rs","crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/nethost.rs","crates/spt-daemon/src/update.rs","crates/spt-daemon/tests/endpoint_survival.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt/src/cli.rs","crates/spt/src/rc.rs","crates/spt/tests/brain_survive.rs","crates/spt/tests/daemon_refresh_e2e.rs","crates/spt/tests/n1_pairing.rs","docs/KNOWN-HAZARDS.md","docs/REDISPATCH-TRUTH-TRIAGE.md","docs/RELEASE-RUNBOOK.md","docs/adr/0038-broker-stream-lifecycle-truth.md","traceable-reqs.toml"],"modifiedFiles":[]},"fromExtension":false,"preserveData":{"snapcompact":{"frames":[{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":13916,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"},{"data":"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","mimeType":"image/png","cols":196,"rows":71,"chars":2935,"font":"8x13","variant":"bw","lineRepeat":1,"detail":"original"}],"totalChars":100348,"truncatedChars":0,"text":"# User ¶█Complete the assignment below, thoroughly:█# Target█Release metadata, changelog, release commit, and requirement/ADR changes for v0.34.0 affecting broker image refresh, redispatch, session stream survival, or PTY handling. Non-goal: broad unrelated release review.█# Change█Determine intended v0.34 behavior and whether a known timing/compatibility tradeoff matches the reported symptom. Cross-check docs against implementation and tests.█# Acceptance█Report relevant release changes and missing test coverage that could allow a 20-30 second post-start/sporadic stall regression.█# Assistant ¶█_**Planning coordinate and mapping tasks**_█# Tool call ¶█//Coordinating release scope█irc(op=\"send\", to=\"V034DiffRca\", message=\"I own v0.34 release contract/docs/changelog/ADR cross-check. Please share any commit/diff findings around broker refresh, redispatch, streams, PTY; I'll avoid broad archaeology duplication.\", replyTo=null, await=false, from=null, timeoutMs=null, peek=null)█<out>█\u000eDelivered to 1 peer(s):█- V034DiffRca: injected\u000f█</out>█# Tool call ¶█//Mapping release artifacts█glob(path=\"CHANGELOG*; VERSION*; docs/**/*; .github/**/*; **/*release*; **/*ADR*; **/*adr*\", hidden=true, gitignore=true, limit=500)█<out>█\u000eCHANGELOG.md█# docs/█MESH-RECOVERY-TRIAGE.md█KNOWN-HAZARDS.md█RELEASE-RUNBOOK.md█REDISPATCH-TRUTH-TRIAGE.md█STORAGE.md█PROJECT-INDEX-TRIAGE.md█THE-FORKENING-TRIAGE.md█UPDATE-WEDGE-2-ROUND3-CODEREAD.md█UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md█PUMP-W2-DESIGN.md█PUMP-W2-GATE-HANDOFF.md█PUMP-W2-BUILD-DELTA.md█PUMP-W2-BUILD-GO.md█MSG-IDENTITY-DISPATCH.md█GATEWAY-LIVENESS-DISPATCH.md█GATEWAY-RCA-STEP1.md█F-035-RCA.md█F-035-RELAY-RCA.md█F-035-CONFLICT.md█PUMP-TRUTH-TRIAGE.md█PUMP-W2-DISPATCH-RULING.md█MUSL-RELEASE-HANDOFF.md█MUSL-W3-DISPATCH-RULING.md█MUSL-W1-DISPATCH-RULING.md█NEXT-MILESTONE-MUSL-TIER-TRIAGE.md█W6-GATE-VERDICT.md█W6-DISPATCH-RULING.md█W5-WAVE-GATE-REPORT.md█W5-GATE-VERDICT.md█W5-GATE-HOLD.md█W5-DISPATCH-RULING.md█W4-WAVE-GATE-REPORT.md█W4-GATE-VERDICT.md█W4-DISPATCH-RULING.md█W3-WAVE-GATE-REPORT.md█W3-GATE-VERDICT.md█W3-SLICE1-REPORT.md█W2-GATE-VERDICT.md█W3-DIGEST-GENERATION-UNION-RULING.md█W3-ENDPOINT-SURVIVAL-DESIGN.md█W2-DESIGN-RULING.md█W2-BUILT-REPORT.md█W2-DESIGN-PROPOSAL.md█NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md█INJECT-SETTLE-REARM-GATE-VERDICT.md█NEXT-MILESTONE-WORKERTRUTH-TRIAGE.md█FLAKE-LEDGER.md█TWO-HOST-RUNBOOK.md█NEXT-MILESTONE-REMOTETRUTH-TRIAGE.md█MANIFEST.m [...1809ch elided...] not-rehome.md█0011-branchstore-git-kv-state-substrate.md█0006-multi-subnet-membership-model.md█0007-subnet-notification-primitive.md█0003-multi-instance-node-anchored-identity-model.md█0004-single-daemon-broker-brain-split-and-self-update.md█0002-bake-networking-into-core-collapse-spt-node.md█0001-clean-room-fork-from-claude-skill-owl.md█## design/█subnet-presence-display.md█robust-wan-subnet-join.md█v0.16.0-update-arc-and-cli.md█## spikes/█SPIKE-06-idempotent-boundary.md█SPIKE-07-branchstore.md█SPIKE-04-forkpty-parity.md█SPIKE-05-restart-stress.md█SPIKE-03-quic-survival.md█SPIKE-02-iroh-smoke.md█SPIKE-01-broker-handoff.md█## reviews/█STAGE-A-codex-redteam.md█# .github/workflows/█ci.yml█release.yml█# crates/█## spt-daemon/src/█release.rs█## spt/tests/█release_verify_e2e.rs█# releases-repo/\u000f█</out>█# Tool call ¶█//Finding version references█grep(pattern=\"0\\\\.34\\\\.0|v0\\\\.34|0\\\\.33\\\\.0|v0\\\\.33\", path=\".\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# CHANGELOG.md█42|█*43|## [0.34.0] - 2026-07-16█44|█45|A stability fix for live remote sessions during a daemon update.█46|█...█56|█*57|## [0.33.0] - 2026-07-16█58|█59|A performance and identity release. The commands that list endpoints and choose█60|a run now answer in a fraction of a second instead of many seconds, `spt whoami`█# docs/█## REDISPATCH-TRUTH-TRIAGE.md█2|█*3|> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first█4|> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:█5|> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`█6|> (hertz source-level, doyle seam-verified same day). Decision record:█...█41|2/3): 4/5 endpoints frozen across both 2026-07-16 doyle refreshes█*42|(deployah-recovery + the v0.33.0 apply) + lia on the update;█43|`DISPATCH:N:Failed` lines with low/historical stream ids in fresh█44|generations = the indiscriminate-replay supporting evidence.█45|█## RELEASE-RUNBOOK.md█52| with no gap. Inserting the new heading with an editor whose anchor spans the█*53| previous heading can silently *consume* that heading (v0.34.0 did this to█*54| `## [0.33.0]`), folding the prior rel [...2331ch elided...] ing, pid, net_up, pump_heartbeat_ms, managed_by, managed_active, subnets[], local_endpoints[], broker_image?, broker_stale?, stall_evict_count?, stall_evict_last_ms?, project_index? }` - `project_index` *(since v0.33.0)* is the index writer's health block: `{generated_ms, source_generation, pending_refresh, last_run_ms, last_duration_ms, last_error?, endpoints, projects, cwds, cwd_cache_hits, cwd_cache_misses, stale_reads, repairs, last_cycle:{branch_enumerations,tree_scans,d...█104|| `subnet status [--nodes]` | `{ daemon_running, subnets[] }` - each `{name, node_count, endpoint_count, nodes[]}` |█105|| `subnet show-code` | `{ subnet, code, otpauth_uri? }` |█106|| `notif list` | `{ notifs[] }` - each `{notif_id, subnet, kind, state, from_id, head}` |█[Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Finding refresh contracts█grep(pattern=\"image refresh|refresh image|broker image|refresh.*broker|broker.*refresh|redispatch|re-dispatch\", path=\".\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# CONTEXT.md█387|█*388|**redispatch** (REDISPATCH-TRUTH, ADR-0038): the fresh brain's reconstruction of target-side attach workers over broker-held peer streams after a **brain cycle** (`spt daemon refresh` / `spt update` apply - the PTYs and QUIC streams survive; the brain-owned serve workers do not). Redispatch eligibility is **lifecycle-gated**: finished/terminal stream rows are retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach - the KNOWN-HAZARDS 7.41 frozen-PTY steal/clear...█*389|_Avoid_: conflating redispatch with msg dispatch or session-cursor resume; filtering replay by origin identity (the legitimate same-`by` successor re-take must keep working - lifecycle is the discriminator).█390|█391|<!-- [doc->REQ-RC-KEY-VT-TRANSLATE] -->█392|**rc keyboard input (Windows VT translation, v0.13.0 bug 2).** On **Windows** an interactive `spt rc` console reads crossterm **key events** and translates each to **standard xterm VT** (`translate_key_event`) - arrows / Home / End / PgUp/Dn / Insert / Delete / F-keys + modifiers all reach the harness as the universal terminal contract (**agnostic**, NOT win32-input-mode; the legacy console deli [...19621ch elided...] with `spt daemon stop` then `spt daemon start`.\"█4219| ),█4220| None => format!(█*4221| \"broker image: not reported (the running daemon predates version reporting, or could \\█4222| not be queried) - if it is older than the installed {installed}, restart with \\█4223| `spt daemon stop` then `spt daemon start` to load {installed}.\"█4224| ),█### tests/█#### daemon_refresh_e2e.rs█282| broker_still_running,█*283| \"REQ-DAEMON-REFRESH: the broker process must survive the refresh untouched \\█284| (pid {broker_pid}).\\n=== brain stderr ===\\n{brain_stderr}\"█285| );█286| assert!(█Showing files 1-20 of 28. Use skip=20 for the next page, or narrow paths/pattern.█[Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Finding stream PTY contracts█grep(pattern=\"stream survival|surviv.*stream|session stream|ConPTY|PTY|pseudo.?terminal|20.?30|30.?second|timeout.*30|30000|25_?000|20_?000\", path=\"docs; CHANGELOG.md; README.md; src; packages; test; tests\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# docs/█## BROKER-BRAIN-SPLIT-RESTORATION.md█50|█*51|- **broker** (stable kernel) - holds *only* the un-transferable, must-not-die resources: PTY master fds, spawned harness child processes, listening network sockets, and (per the §B ownership table) the Iroh/QUIC endpoint + conn table. Minimal, versioned local IPC. Almost never updates.█52|- **daemon brain** (userspace) - all logic (routing, registry, pulse/psyche loops, manifest parsing, update orchestration). Restarts freely on update; rehydrates from disk and re-attaches to broker-held handles.█53|█54|Explicit evidence the split was meant to be a **process** boundary:█...█57|- `M3a-PLAN.md`, `M3-PLAN.md`, `M3b-PLAN.md` all name \"the broker **process** + IPC (M3b).\"█*58|- Spikes 01/03/04/05/06 each **PROVEN** with *two separate binaries* (`spt-spikes/spike-01-broker-handoff`): a PTY child + a live QUIC transfer survive 100× brain restarts, gapless and exactly-once. A spike that proves \"survives a brain restart\" presupposes the broker is a separate process.█59|█60|### 2.2 What production actually does█61|█...█72|█*73|- The brain cannot restart onto a new binary without killing the in-process broker thread - which would c [...48849ch elided...] 5 controller/viewer model (doyle ruling 2026-06-14, Q1).█## MANIFEST.md█129|<!-- [doc->REQ-PSYCHE-CONTEXT-FILE-INDIRECTION] -->█*130|**Psyche-download - `{psyche_context_file}` (file-always, replaces `{psyche_context}`).** The composed Psyche mind rides a **file**, never the command argv: before each turn spt-core writes the mind into the nested psyche perch dir and fills a single **`{psyche_context_file}` = that path** (argv-cap-immune - a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / re...█131|█132|<!-- [doc->REQ-PSYCHE-NESTED-RESOLUTION] -->█[Showing lines 1-276 of 531 (49.9KB limit). Use :277 to continue. Some lines truncated to 512 chars]\u000f█</out>█# Assistant ¶█_**Tracing release commit via git metadata**_█# Tool call ¶█//Reading release notes█read(path=\"CHANGELOG.md\", selector=\"35-85\")█<out>█\u000e## [0.35.0] - 2026-07-16█...█### Fixed█...█- **A node no longer strands itself from its subnets after a brief failure to█...█Routes are now kept and marked unreachable rather than deleted, and address█resolution falls back to the roster, so the node recovers on its own with no█manual state surgery.█- **Poisoned peer-address entries are repaired at startup.** If a cached peer█address has come to claim a different peer's identity, the daemon now detects█and repairs it from the subnet roster when it starts - and says so loudly in█the log - rather than carrying the bad entry forward.█## [0.34.0] - 2026-07-16█A stability fix for live remote sessions during a daemon update.█### Fixed█- Updating or refreshing the daemon - `spt update`, `spt update --restart`, or█`spt daemon refresh` - no longer disturbs a remote session you are attached to.█A daemon cycle could previously replay already-finished output onto a live█remote terminal: old text re-typed itself, control of the session was stolen,█or a running session was left frozen. The daemon now treats a finished█session's history as terminal and never re-drives it onto a live terminal, so█the remote sessions you are a [...1108ch elided...] picker, and `spt api endpoint-info` read a█project index the daemon keeps current, instead of inspecting each project's█git state on every call. On a node with many endpoints this takes these█commands from many seconds to well under a second. Two notes on the trade-off:█a project you just changed may show its previous attribution for a brief moment█(the index refreshes on change and reconciles periodically), and on a node█whose daemon has not yet built the index, attribution shows as `-` until the█first build lands.█### Added█- `spt daemon status` now reports the health of the project index that powers the█fast listings - when it was last generated, how many projects it covers, the█...█health - the status report is.█...█[Showing lines 34-88 of 1126. Use :89 to continue]\u000f█</out>█# Tool call ¶█//Reading redispatch ruling█read(path=\"docs/REDISPATCH-TRUTH-TRIAGE.md\", selector=\"1-180\")█<out>█\u000e# REDISPATCH-TRUTH - milestone triage (JIT)█> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first█> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:█> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`█> (hertz source-level, doyle seam-verified same day). Decision record:█> ADR-0038. Lane: doyle triages -> todlando builds -> doyle gates -> deployah█> releases -> hertz field-verify (they hold the incident logs).█## Why (field: 4/5 endpoints frozen on one refresh, twice in one day)█`spt daemon refresh` / `spt update` (same brain-cycle path, `applyhost.rs`)█freezes active remote `spt rc` sessions. The PTYs and broker-held QUIC█streams survive by construction - the freeze is a **production redispatch█bug** in the fresh brain's stream reconstruction:█1. **Stale replay steals live controllers.** The fresh█`dispatch::run_dispatch_loop` starts with an EMPTY per-process `claimed`█set (dispatch.rs:206) and its claim condition█(`initiated_locally || next_seq == 0 || !claimed.insert`, dispatch.rs:214)█has NO finished filter - while `NetShared.streams` has NO removal path█(single insert nethost.rs ~649; `StreamLog::finis [...5350ch elided...] eqs check` green.█## Possible kin (NOT folded - verify, don't assume)█- deployah zombie controlled-by latch (REQ-ENDPOINT-CYCLE-HONEST seed,█daemon-lifecycle cluster): plausibly the same stale-stream fingerprint,█but unproven. The seed stands in its own cluster unless W1 evidence█shows the same root; do not widen this milestone on speculation.█- stall-evict 15->17 baseline shift: likely same fingerprint; re-measure at█field-verify, no dedicated work.█## Explicitly out of scope█- MESH-RECOVERY (sibling milestone candidate, separate contract; next in█line).█- Daemon-lifecycle cluster (endpoint-cycle-honest + comeback + massacre +█orphan-listener).█- Any change to attach-intent semantics, controller/viewer model, or the█brain session-cursor resume path (0.30.5/6 fix stands).█\u000f█</out>█# Tool call ¶█//Reading lifecycle ADR█read(path=\"docs/adr/0038-broker-stream-lifecycle-truth.md\", selector=\"1-260\")█<out>█\u000e# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)█- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day)█- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`█## Context█The broker's `NetShared.streams` table is append-only in practice: rows are█inserted once (nethost.rs ~649) and `StreamLog::finish` only marks EOF -█no removal or retirement path exists. Stream *classification* is derived at█redispatch time by peeking ring seq 0, but the `StreamLog` data ring is█bounded (4096 transport chunks) and evicts the protocol opener under█traffic. Dispatcher claims are a per-process one-shot `HashSet` insert█performed before worker spawn, never cleared or retried.█After any brain cycle (`spt daemon refresh`, `spt update` apply - same█path), the fresh `dispatch::run_dispatch_loop` therefore replays EVERY█broker-held peer stream, including finished historical attach streams. A█replayed historical Attach silently steals the live controller█(same-identity `become_controller`, no `Displaced`) and its replayed EOF█clears it - the operator sees a frozen remote PTY (field: 4/5 end [...2053ch elided...] reams survive opener eviction - verified by production-path█regressions (the prior e2e manually re-served a stream and bypassed█rediscovery entirely).█- The stream table gains a retirement duty (bounded growth instead of█forever-discoverable rows) and a small per-stream metadata cost (one█pinned opener record per stream, ring untouched).█- Rejected alternatives: clearing the whole stream table on brain restart█(destroys live streams' reconstruction facts along with the stale ones);█enlarging the data ring to preserve openers (unbounded memory for a█correctness fact); filtering replay by origin identity (breaks the█legitimate successor re-take, the exact silent-steal seam); a█per-generation claimed-set handoff (patches the symptom, keeps terminal█rows eligible forever).█\u000f█</out>█# Tool call ¶█//Reading release workflow█read(path=\"docs/RELEASE-RUNBOOK.md\", selector=\"1-110\")█<out>█\u000e# Release runbook█> M6-D6/D7 (ADR-0015). How a release ships. CI builds; the maintainer signs█> locally - release keys never enter CI.█<!-- [doc->REQ-REL-2] -->█## One-time setup (done 2026-06-05)█- Key ceremony: `cargo run -p xtask -- release-keygen rel-primary-2026` (and█`rel-recovery-2026`); both seeds in the password manager (+ separate paper█backup); both PUBLIC keys embedded in `BUILTIN_RELEASE_KEYS`█(`crates/spt-daemon/src/release.rs`). The recovery key is never used until█the primary is lost/compromised - then it signs the next release, which█rotates in a fresh primary.█- `RELEASES_TOKEN` repo secret on spt-bs-core: fine-grained PAT, Contents█read/write on `BigscreenVR/spt-bs-releases` only.█## Per release█1. **Bump inside the release PR - never as a separate push to `main`.** Put the█version bump (`[workspace.package] version` in root `Cargo.toml` **and** the█matching first-party `spt-*` lines in `Cargo.lock` - leave third-party lines█alone), the regenerated docs (`cargo run -p xtask -- gen`), and the changelog█section (step 2) **in one PR**: the feature PR's final commit, or a dedicated█release PR when the work already merged. That PR's CI [...2964ch elided...] metadata█assets, and flips the draft public. `--version <N>` is the **monotonic█release counter** (u64; strictly increasing across releases - it is not█the semver). `SPT_RELEASE_SEED` (raw hex) works instead of█`_CMD` when pasting from the manager by hand.█## Notes█- The wire-protocol version (REQ-ARCH-3) and the release counter and the█workspace semver are three independent numbers. Never conflate.█- The installer scripts trust HTTPS + `SHA256SUMS` on first fetch; the█`*.release.json` metadata is what `spt update` verifies thereafter against█the embedded two-key anchor.█- Revoking a leaked key without a rebuild: add its id to `\"revoked\"` in█`identity/release-keys.json` on each node (the file overlays the builtin█set); ship the next release signed by the other key.█\u000f█</out>█# Tool call ¶█//Finding requirement registry█glob(path=\"*req*; **/*req*; **/*REQ*; traceable-reqs.toml; Cargo.toml; crates/*/Cargo.toml; .git/refs/tags/*; .git/logs/refs/*/*; .git/packed-refs\", hidden=true, gitignore=false, limit=300)█<out>█\u000eCargo.toml█traceable-reqs.toml█# target/debug/█## .fingerprint/█### reqwest-ac75cf070f34b907/█lib-reqwest█lib-reqwest.json█dep-lib-reqwest█### reqwest-6b5a94550f6dc659/█lib-reqwest.json█lib-reqwest█dep-lib-reqwest█### reqwest-573a35dd52b00aaa/█lib-reqwest█lib-reqwest.json█dep-lib-reqwest█### reqwest-17cb2a5694bd2db8/█lib-reqwest.json█lib-reqwest█dep-lib-reqwest█## deps/█libreqwest-ac75cf070f34b907.rlib█libreqwest-ac75cf070f34b907.rmeta█reqwest-ac75cf070f34b907.d█reqwest-6b5a94550f6dc659.d█libreqwest-6b5a94550f6dc659.rmeta█reqwest-573a35dd52b00aaa.d█libreqwest-573a35dd52b00aaa.rmeta█libreqwest-17cb2a5694bd2db8.rlib█libreqwest-17cb2a5694bd2db8.rmeta█reqwest-17cb2a5694bd2db8.d█## incremental/█### idempotent-31j5k8fm1ian5/█s-hkgm7eei2j-0kbreqr.lock█#### s-hkgm7eei2j-0kbreqr-d9u0l9wezcls705t3khyv2ixc/█### psyche_context_file_e2e-2dqrb136ok7yi/s-hkglrh8sl6-0vpv7rr-dreqqj75ul14od8on9hlgvak0/█### control_stamp_lifetime-2g8xrd5s2zzcg/█#### s-hkglsjmrbq-0iryj36-65n0e2gs1qvewlmzm025dd0ju/█akuboki46oreqkv9052w0rsat.o█#### s-hkgm8242u0-0m58x17-a4wp2rh6hdl2yz11bonqsynl5/█akuboki46oreqkv9052w0rsat.o█### xlate_choreo_fixture-1c2lrtygf8rg8/█#### s-hkglsp4uww-0tu2p1j-14a4aab8pm26q4m78qeestim0/█2rg6i [...3357ch elided...] -1hxcbta-1yvu6lvva4ua71k85tnq89cwy/█0vobjkuz3q46ovjzg1dlkreqg.o█### s-hjlucsmvm1-13phy1f-f3gzswttisc5juzw21iaeufj9/█0vobjkuz3q46ovjzg1dlkreqg.o█## .fingerprint/█### reqwest-17cb2a5694bd2db8/█lib-reqwest.json█lib-reqwest█dep-lib-reqwest█### reqwest-573a35dd52b00aaa/█lib-reqwest.json█lib-reqwest█dep-lib-reqwest█### reqwest-ac75cf070f34b907/█lib-reqwest.json█lib-reqwest█## deps/█libreqwest-17cb2a5694bd2db8.rlib█libreqwest-17cb2a5694bd2db8.rmeta█reqwest-17cb2a5694bd2db8.d█reqwest-573a35dd52b00aaa.d█libreqwest-573a35dd52b00aaa.rmeta█libreqwest-ac75cf070f34b907.rlib█libreqwest-ac75cf070f34b907.rmeta█reqwest-ac75cf070f34b907.d█## build/aws-lc-sys-66e6bfff003e2083/out/█01ff1b89ed0b6d38-x_req.o█01ff1b89ed0b6d38-x509_req.o█01ff1b89ed0b6d38-t_req.o█[200 results limit reached. Use limit=400 for more]\u000f█</out>█# Tool call ¶█//Finding release contract tags█grep(pattern=\"REQ-DAEMON-REFRESH|REQ-REDISPATCH|REQ-STREAM-OPENER-DURABLE|REQ-DISPATCH-CLAIM-RETRY|BROKER-VIEWER-BRAIN-DECOUPLE|0\\\\.34\\\\.0\", path=\"requirements; traceable-reqs.toml; docs; crates; CHANGELOG.md; Cargo.toml\", selector=null, case=true, gitignore=true, skip=null)█<out>█\u000e# traceable-reqs.toml█2109|[[requirements]]█*2110|id = \"REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE\"█2111|title = \"W2 (LIFECYCLE-TRUTH, KNOWN-HAZARDS, flagship - the update wedge): PTY viewer fan-out and control mutations must not depend synchronously on a live draining brain. ROOT rig-CONFIRMED (NtSuspendProcess on the brain, no update involved): brain-subscriber session-output writes ride UNDER the per-session log lock (broker.rs:19-20); failed writes are handled (cursor freeze + detach :3486) but BLOCKED writes are not. Suspended brain => within seconds attached rc output freezes; detach does NOT releas...█2112|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"] # ACTIVATED LIFECYCLE-TRUTH W2 (todlando 2026-07-07) per doyle ruling docs/W2-DESIGN-RULING.md @60ec0ed (minimal-plus scope). doc = KNOWN-HAZARDS entry (\"PTY fan-out + broker control plane never block on any single subscriber conn - bounded sinks everywhere incl. the brain conn; brain.ready != subscribers drained\"). impl = (a) bounded brain-conn sink (writer thread + bounded queue + BRAIN_WRITE_DEADLINE=15s deadline-evict, full-with-zero-progress last_o...█2113|█...█2140|id = \"REQ-UPDATE-PROMOTE-DRAINED\"█*2141|title = \"W3 [...34668ch elided...] er sweep leaves nothing for a█2179| // future dispatcher generation to replay on a transport that can never█2180| // serve again (the field bug's historical-rows-forever half).█## tests/█### brain_decouple.rs█*1|//! LIFECYCLE-TRUTH W2 (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) - the suspend-brain█2|//! int rig, on a REAL broker hosting a REAL PTY child over the real local IPC.█3|//!█4|//! ## The bug this gate locks down (the update-wedge root)█...█219|█*220|// [int->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]█221|#[test]█222|fn suspended_brain_controller_is_stall_evicted_take_completes_viewer_ticks() {█223| init_home();█Showing files 1-20 of 25+. Use skip=20 for the next page, or narrow paths/pattern.█Skipped missing paths: requirements█[Some lines truncated to 512 chars]\u000f█</out>█# Assistant ¶█_**Reading release tag hashes**_█# Tool call ¶█//Reading release tag target█read(path=\".git/refs/tags/v0.34.0\", selector=\"raw\")█<out>█\u000eb7b074c2509c1015fe341185662b4622d661f274█\u000f█</out>█# Tool call ¶█//Reading prior tag target█read(path=\".git/refs/tags/v0.33.0\", selector=\"raw\")█<out>█\u000e21a46d5560819e505c83f0908b7b562d65b7d1ac█\u000f█</out>█# Tool call ¶█//Reading main reflog tail█read(path=\".git/logs/refs/heads/main\", selector=\"120-\")█<out>█\u000e90852bf1bfb8832bf33a27ee58541be2618ec592 18d50459c1849d5ed58d427447871f870ff47edd Reavo End <decidel@gmail.com> 1782301160 -0700 pull: fast-forward█18d50459c1849d5ed58d427447871f870ff47edd 9e5b33bb3ed1f6ed6766f5f17373e43ce2778932 Reavo End <decidel@gmail.com> 1782405982 -0700 pull: fast-forward█9e5b33bb3ed1f6ed6766f5f17373e43ce2778932 a8baa237c666a529459adc5996990686b388faa6 Reavo End <decidel@gmail.com> 1782650984 -0700 pull: fast-forward█a8baa237c666a529459adc5996990686b388faa6 461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 Reavo End <decidel@gmail.com> 1782848574 -0700 pull: fast-forward█461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 aebabaa2f7dae538c25fa8c21ab7c9edbc643114 Reavo End <decidel@gmail.com> 1782943485 -0700 pull --ff-only origin main: Fast-forward█aebabaa2f7dae538c25fa8c21ab7c9edbc643114 9bae13e4136d22f4b5d5d6757b6f206390bfba6a Reavo End <decidel@gmail.com> 1783070613 -0700 reset: moving to origin/main█9bae13e4136d22f4b5d5d6757b6f206390bfba6a 289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 Reavo End <decidel@gmail.com> 1783121597 -0700 reset: moving to origin/main█289bdcdc9431c55ed7228cc7544a11f4bd9ea4d1 188fe1a9f50a8bbc3234263bb16662a17b51a94e Reavo End <decidel@gmail.com> 1783149421 [...9712ch elided...] <decidel@gmail.com> 1784206458 -0700 pull origin main: Fast-forward█01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700 pull --ff-only: Fast-forward█b7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700 merge origin/main: Fast-forward█d41ceaab197af6ebf1da842400dff3a6518084bb 0bb37f352d871f288d3c1ab5af6133020d57c674 Reavo End <decidel@gmail.com> 1784239433 -0700 cherry-pick: triage(mesh-recovery): milestone JIT - ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first█0bb37f352d871f288d3c1ab5af6133020d57c674 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784246971 -0700 merge origin/main: Fast-forward█\u000f█</out>█# Tool call ¶█//Reading packed release refs█read(path=\".git/packed-refs\", selector=\"raw\")█<out>█\u000e# pack-refs with: peeled fully-peeled sorted█1941eb823b36cc6ecc95dcbffe49b5a2047335fd refs/heads/design/endpoint-creation-flow█c5887db27f6c8576decec071c073e76c5144ba66 refs/heads/dev-freeform█f747e1e7048831b51c8aed18a611d10238c3eda4 refs/heads/docs/msg-envelope-adr-0020█461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/doyle-work█aa5fa2a42e7ab13045d1827eefcddade797bac73 refs/heads/feat/translate-proof█3da4aa269253b7b8bf0b3c9dca31c6e5ee2f51d1 refs/heads/field-bug-milestone█b4275c3c4fcce720bf244b32f42a34b18d419443 refs/heads/fix/digest-proof-session-id-keys█4fd028d5ceda811557f361162f50987db929eb02 refs/heads/fix/f019-translation-install-dir-resolve█60fadd25f3b62712607822692d4b7596b4a68de3 refs/heads/fix/v0143-raw-inject-removal█be3d207723b2e59b01bbbc58151221af0504c58f refs/heads/fix/win-program-resolve█5229bbc7455f144bddfa7acfe877a36221fdaf21 refs/heads/m10-digest-extractor█672b9288e5669c023bb3774abf8a339165e70082 refs/heads/m11-shell-substrate█2da139504d9abeaae4ad0430b47a0dc1f64648ef refs/heads/m12-w1-bringup-rc█461e6fe744d99cb967e052a8ecb3b14bbea3d4f2 refs/heads/main█08c476fe81d3c4c26812917d9acc28ef6000611c refs/heads/pump-carrier-fix█74e1a9b6b1db086adafd6ae340aa4ba53e3b0510 ref [...6237ch elided...] 0dd604d378c77233876ff9f9d895d6ff7e refs/tags/v0.7.0█cb0560052c7392d386d9e68db359bbbf3138dcfe refs/tags/v0.7.1█1582e32ba704e7cad770a42265b5c90e044ace37 refs/tags/v0.7.2█22a9c58cfb7b5259dff99670012108095cb3e376 refs/tags/v0.7.3█70294ef2c20f3b07866eb97f9e9ac60ee61460e9 refs/tags/v0.8.0█325431aeaa321d26bca4cc83e8769bb84b77d9c7 refs/tags/v0.8.1█4ac17c29978c0a087682a343cef4befc1aff9c3d refs/tags/v0.8.2█8ed7fcf4ec1db98a36b3baf9e34a162a2a9a1d96 refs/tags/v0.8.3█^b6470c1c4ababb407d44ed2c38b4354485f0b6b1█29a71705483f0fc3f5a9c726b7972d1142276e0e refs/tags/v0.8.4█^c9ff19f1fd313b312395cdf4eace60cbe677e007█a1243ee1585e94674ee38b0858ef1aafb2465875 refs/tags/v0.9.0█^c448b321bef3ea452184f974f2c6b547de058653█ab1d738361d9c607e0a918f9839fda96da723839 refs/tags/v0.9.1█^7f93ecd59a900f4890f787dd56e2c69d8ededaec█\u000f█</out>█# Tool call ¶█//Reading workspace version█read(path=\"Cargo.toml\", selector=\"1-30\")█<out>█\u000e# spt-core cargo workspace (M0). Acyclically-layered crates per R-ARCH-1:█# spt-proto -> spt-store -> spt-msg -> {spt-net, spt-term, spt-runtime} -> spt-live -> spt-daemon -> spt█# M0 builds the bottom two: spt-proto (wire) + spt-store (persistence).█# M1 adds spt-msg (local messaging) - third layer, one-way above spt-store.█# M2a adds spt-runtime (harness contract: manifest schema + AgentRuntime) - fourth layer.█# M2b adds spt-live (live-agent lifecycle executor) - fifth layer, daemon-bound (not public SDK).█# M3a adds spt-term (session-surface mechanism: ConPTY/forkpty PTY layer) - sibling of spt-runtime, above spt-msg.█# M3b adds spt-daemon (broker/brain supervisor) - seventh layer, above spt-live, below spt; internal (not public SDK).█# M4 adds spt-net (P2P WAN/LAN transport) - sibling of spt-term/spt-runtime, above spt-msg; internal, default-on `net` feature.█[workspace]█resolver = \"2\"█members = [\"crates/spt-proto\", \"crates/spt-store\", \"crates/spt-msg\", \"crates/spt-net\", \"crates/spt-term\", \"crates/spt-runtime\", \"crates/spt-live\", \"crates/spt-daemon\", \"crates/spt\", \"adapters/mock\", \"crates/xtask\"]█# M6-D6 (grill decision 9): ONE shared version for every crate and the binary█# - the product version (v0.1.0 tag, `spt --version`). The wire-protocol█# version stays its own counter (REQ-ARCH-3) and the SignedRelease `version`█# is the separate monotonic release counter; neither conflates with this.█[workspace.package]█version = \"0.35.0\"█\u000f█</out>█# Tool call ¶█//Reading workflow note extraction█read(path=\".github/workflows/release.yml\", selector=\"1-180\")█<out>█\u000e# M6-D6: the tag-triggered release pipeline (grill decision 9, ADR-0015).█#█# CI's half of the ceremony: build the platform binaries on both self-hosted█# runners, assemble the asset set the self-updater consumes (binaries,█# SHA256SUMS, manifest.schema.json, mock-adapter.zip, and the docs bundle█# spt-docs.tar.gz - ADR-0036 §4), and create a DRAFT release on the private█# channel BigscreenVR/spt-bs-releases (cross-repo via the RELEASES_TOKEN█# fine-grained PAT).█#█# The maintainer's half stays manual and local (no signing keys in CI) -█# normally one command that downloads, verifies, signs (binaries + the docs█# entry in the update set), uploads, and flips the draft:█# SPT_RELEASE_SEED=<seed> cargo run -p xtask -- release-publish \\█# --tag <vX.Y.Z> --key-id rel-primary-2026 --version <N>█#█# [impl->REQ-REL-2]█# [impl->REQ-RELEASE-CHANNEL-PRIVATE]█name: release█on:█push:█tags: [\"v*\"]█jobs:█build:█strategy:█fail-fast: false█matrix:█# Asset names MUST match the SUPPORTED_PLATFORMS registry rows█# (crates/spt-daemon/src/release.rs) - the update-set + installer key on█# them. `target` empty = the host-default toolchain (gnu / msvc [...6505ch elided...] -CHANNEL-PRIVATE] the v0.32.0 (counter 59) ceremony█# exercises this assemble/draft leg end-to-end against the private█# channel - the first release on BigscreenVR/spt-bs-releases (W2 ruling:█# the release ceremony is the integration evidence, no test rig).█env:█GH_TOKEN: ${{ secrets.RELEASES_TOKEN }}█run: |█gh release create \"${GITHUB_REF_NAME}\" \\█--repo BigscreenVR/spt-bs-releases \\█--draft \\█--title \"spt ${GITHUB_REF_NAME}\" \\█--notes-file dist/RELEASE_NOTES.md \\█dist/spt-x86_64-* \\█dist/SHA256SUMS \\█dist/manifest.schema.json \\█dist/mock-adapter.zip \\█dist/spt-docs.tar.gz█[Showing lines 1-183 of 184. Use :184 to continue]\u000f█</out>█# Tool call ¶█//Finding release branch history█grep(pattern=\"0\\\\.34\\\\.0|redispatch|release.*34|release:|v0\\\\.34\", path=\".git/logs/refs; .git/logs/HEAD\", selector=null, case=false, gitignore=false, skip=null)█<out>█\u000e# .git/logs/█## HEAD█361|d24886a110814925c853dc0b12fd16e010a8d92a bc3c1f2e4cce5e5de55bd75602647d3cab84d428 Reavo End <decidel@gmail.com> 1780973376 -0700 commit: docs(changelog): daemon-lifecycle UX + concurrent --nodes probes (Unreleased)█*362|bc3c1f2e4cce5e5de55bd75602647d3cab84d428 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974008 -0700 commit: release: v0.3.0█363|35d1672fdf999a4fc6e58fcd04ff5c953452beb0 4fa29498d2552f277e918219f74d9c75183a6921 Reavo End <decidel@gmail.com> 1780974018 -0700 checkout: moving from dev-freeform to main█364|4fa29498d2552f277e918219f74d9c75183a6921 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974019 -0700 merge dev-freeform: Fast-forward█365|35d1672fdf999a4fc6e58fcd04ff5c953452beb0 35d1672fdf999a4fc6e58fcd04ff5c953452beb0 Reavo End <decidel@gmail.com> 1780974701 -0700 checkout: moving from main to dev-freeform█...█379|c44e62209cc5a4c6c6ab9d6cfb247728e1974d9b 63fbbee31235a5107d2ccdb364efd77d16af2e6f Reavo End <decidel@gmail.com> 1780980823 -0700 commit: feat(update): `spt update fetch` - origin-source bootstrap from GitHub (REQ-UPD-7)█*380|63fbbee31235a5107d2ccdb364efd77d16af2e6f 51646 [...29109ch elided...] 7 -0700 commit: release: v0.33.0 (counter 60) - PROJECT-INDEX milestone█#### v0.34.0█1|0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700 branch: Created from 01d2d18█*2|01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700 commit: release: v0.34.0 (counter 61) - REDISPATCH-TRUTH W1█#### v0.35.0█1|0000000000000000000000000000000000000000 7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 Reavo End <decidel@gmail.com> 1784247046 -0700 branch: Created from 7f5ead8█*2|7f5ead8391528e7f356cb0e4e0dc6050f7575bf5 83f90e7e622a0693ae8e6f929c5129352f08b818 Reavo End <decidel@gmail.com> 1784247250 -0700 commit: release: v0.35.0 (counter 62) - MESH-RECOVERY W1\u000f█</out>█# Assistant ¶█_**Investigating release commit discrepancies**█**Analyzing broker and brain version compatibility**█**Identifying durable opener capture in v34**_█# Tool call ¶█//Reading release history window█read(path=\".git/logs/HEAD\", selector=\"1448-1470\")█<out>█\u000e53bae678013031f01391c68e736f730f5bda148e afd01692c68827e87f9569d899a7b9efc6137d83 Reavo End <decidel@gmail.com> 1784185248 -0700 commit (merge): Merge remote-tracking branch 'origin/main' into build/project-index-w3-reader-cutover█afd01692c68827e87f9569d899a7b9efc6137d83 d88fec6696ea080c32107dc727f5155738e2f2a2 Reavo End <decidel@gmail.com> 1784186564 -0700 commit: test(project-index-w3): reader e2e fixture commits ep2's alpha slice; re-home the PICKER-HISTORY-FRESH impl tag█d88fec6696ea080c32107dc727f5155738e2f2a2 e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 Reavo End <decidel@gmail.com> 1784188140 -0700 checkout: moving from build/project-index-w3-reader-cutover to main█e0387e637d175f2aef8d4d1a5e65761be0cd2cf1 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188142 -0700 pull --ff-only origin main: Fast-forward█648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 648e4f8d3a720c5be9ef5459ca19ee8787c7d60c Reavo End <decidel@gmail.com> 1784188143 -0700 checkout: moving from main to main█648e4f8d3a720c5be9ef5459ca19ee8787c7d60c 34ae823b2ada1c5098b195d6e5f70422f0d7098d Reavo End <decidel@gmail.com> 1784192029 -0700 checkout: moving from main to release/v0.33.0█34ae823b2ada1c [...3380ch elided...] 93746f8991ac7d961 Reavo End <decidel@gmail.com> 1784236637 -0700 commit: docs(runbook): add contiguous-ladder self-check after new CHANGELOG section█37f0b38862a876e38d2ff4793746f8991ac7d961 9a83afa84ad62d7aaa3866e158927fd929ff3dee Reavo End <decidel@gmail.com> 1784239407 -0700 commit: triage(mesh-recovery): milestone JIT - ADR-0039 peer-route resolution truth + KH 7.42 + 4 REQs registry-first█9a83afa84ad62d7aaa3866e158927fd929ff3dee b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784239433 -0700 checkout: moving from fix/changelog-0330-heading to main█b7b074c2509c1015fe341185662b4622d661f274 d41ceaab197af6ebf1da842400dff3a6518084bb Reavo End <decidel@gmail.com> 1784239433 -0700 merge origin/main: Fast-forward█[Showing lines 1447-1473 of 1484. Use :1474 to continue]\u000f█</out>█# Tool call ¶█//Reading release branch history█read(path=\".git/logs/refs/heads/release/v0.34.0\", selector=\"raw\")█<out>█\u000e0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700 branch: Created from 01d2d18█01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700 commit: release: v0.34.0 (counter 61) - REDISPATCH-TRUTH W1█\u000f█</out>█# Tool call ¶█//Reading redispatch loop█read(path=\"crates/spt-daemon/src/dispatch.rs\", selector=\"155-430\")█<out>█\u000epub fn classify_first_line(line: &serde_json::Value) -> StreamFamily {█match line.get(\"kind\").and_then(|k| k.as_str()) {█...█None => {█...█}█}█}█/// Why a dispatched stream's worker ended - loud + typed, never silent.█#[derive(Debug)]█pub enum DispatchOutcome {█/// The family's serve loop ran to its own outcome (rendered for the log).█Served(String),█/// The first line never arrived / didn't parse - dropped.█Unknown,█/// The worker failed (IO against broker or store).█Failed(String),█}█/// A claimed stream's dispatch lifecycle (ADR-0038 C,█/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`█/// insert performed BEFORE worker spawn and never cleared - one transient█/// worker-setup failure permanently abandoned the stream. Claims now█/// distinguish in-flight vs terminal vs retryable, and a transient failure█/// requeues bounded (backoff-shaped, never a hot loop).█#[derive(Debug, Clone, Copy, PartialEq, Eq)]█enum ClaimState {█/// A worker owns the stream right now (`attempts` = prior failures).█InFlight { attempts: u32 },█/// Done for this dispatcher's lifetime: served, unclassifiable, or█/// ret [...10297ch elided...] R-0038 path.█Err(_) => {}█}█peek_first_line(broker_name, stream_id, floor_seq)█}█/// Peek the stream's first retained NDJSON line on a throwaway brain█/// connection (the pre-ADR-0038 fallback - subject to ring eviction, kept█/// only for the older-broker window). Subscribes from `floor_seq` (the oldest█/// retained chunk) so a rolled ring replays instead of tripping the brain's█/// gap check.█fn peek_first_line(█broker_name: &str,█stream_id: u64,█floor_seq: u64,█) -> io::Result<Option<serde_json::Value>> {█let mut brain = connect(broker_name)?;█brain.net_stream_subscribe(stream_id, floor_seq)?;█let mut buf: Vec<u8> = Vec::new();█loop {█match brain.read_event()? {█...█}█}█}█[Showing lines 154-433 of 1034. Use :434 to continue]\u000f█</out>█# Tool call ¶█//Reading redispatch tests█read(path=\"crates/spt-daemon/tests/redispatch.rs\", selector=\"200-520\")█<out>█\u000efn render_until(█...█) -> bool {█...█while !contains(buf, needle) {█...█match operator.read_event().expect(\"operator read event\") {█...█BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {█...█}█_ => {}█}█}█saw_eof█}█fn screen_of(viewport: &[u8]) -> String {█let mut screen = ScreenGrid::new(24, 80);█screen.advance(viewport);█String::from_utf8_lossy(&screen.render_repaint()).into_owned()█}█// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 - the field bug, end to end█// through PRODUCTION rediscovery: a finished historical Attach and a current█// active Attach on the same endpoint/origin; only the dispatcher generation█// restarts. The historical stream must neither take nor clear the current█// controller: post-restart input still renders (the frozen-PTY symptom is█// the failure), the operator sees NO EOF (the replayed-EOF detach is the█// clear), the fresh generation logs NO replay of the historical stream id,█// and the retired row is GONE from the broker's enumeration█// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).█#[test]█fn finished_historical_attach_neither_takes_nor [...13391ch elided...] tatus().expect(\"a status\").addr;█let gen1 = Generation::spawn(&name_a, dir.path().join(\"gen1.log\"), &[]);█let mut operator = connect_retry(&name_b);█let conn = operator█.net_dial(a_addr, Some(MintedOp::new(Minter::Cli, 1)))█.expect(\"dial\");█let s1 = request_attach(&mut operator, conn.conn_id, sid, 0, MintedOp::new(Minter::Rc, 2), AttachIntent::Control).expect(\"request\");█operator.net_stream_subscribe(s1, 0).expect(\"subscribe\");█send_attach_input(&mut operator, s1, b\"MARK-A\\r\", 10).expect(\"input pre\");█let mut dec = AttachDecoder::new();█let (mut cur, mut view) = (0u64, Vec::new());█render_until(&mut operator, s1, &mut dec, &mut cur, &mut view, b\"MARK-A\", Duration::from_secs(20));█...█}█[Showing lines 199-523 of 621. Use :524 to continue]\u000f█</out>█# Tool call ¶█//Reading refresh integration█read(path=\"crates/spt/tests/daemon_refresh_e2e.rs\", selector=\"180-340\")█<out>█\u000efn daemon_refresh_cycles_brain_while_hosted_endpoint_survives() {█...█while Instant::now() < deadline {█if status().as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {█...█}█std::thread::sleep(Duration::from_millis(150));█}█// -- (5) THE VERB: `spt daemon refresh` - the production CLI path. --█let refresh = {█let mut cmd = Command::new(&spt_bin);█cmd.no_window()█.args([\"daemon\", \"refresh\"])█.env(\"SPT_HOME\", home.path());█output_bounded(cmd, Duration::from_secs(30))█};█let refresh_stdout = String::from_utf8_lossy(&refresh.stdout).to_string();█let refresh_stderr = String::from_utf8_lossy(&refresh.stderr).to_string();█// -- (6) The brain must CYCLE: a new pid stamps brain.ready with a bumped█// generation (no binary swapped anywhere). --█let cycled = wait_ready_not(&ready_path, Some(brain_pid_before), Duration::from_secs(30));█// The broker process must be the SAME OS process (never restarted).█let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);█// The hosted endpoint survives: harness alive + perch back/still ONLINE.█[...2968ch elided...] }) - the supervisor cycle, not a re-stamp.\\n\\█=== brain stderr ===\\n{brain_stderr}\"█);█assert!(█broker_still_running,█\"REQ-DAEMON-REFRESH: the broker process must survive the refresh untouched \\█(pid {broker_pid}).\\n=== brain stderr ===\\n{brain_stderr}\"█);█assert!(█harness_alive_after,█\"REQ-DAEMON-REFRESH: the hosted harness session must STILL be ALIVE across the \\█refresh - only the brain cycles, never the hosted PTYs. harness_pid={harness_pid:?}.\\n\\█=== brain stderr ===\\n{brain_stderr}\"█);█assert!(█still_online,█\"REQ-DAEMON-REFRESH: the endpoint perch must remain ONLINE across the refresh \\█(the session was never torn down).\\n=== brain stderr ===\\n{brain_stderr}\"█);█}█\u000f█</out>█# Tool call ¶█//Reading update apply cycle█read(path=\"crates/spt-daemon/src/applyhost.rs\", selector=\"300-390\")█<out>█\u000e/// the caller uses [`apply_staged_daemonless`] instead - this path REQUIRES a live█/// broker to hand off to (a dead broker fails the apply with the seat untouched).█// [impl->REQ-UPD-3]█// [impl->REQ-UPD-4]█pub fn apply_staged(█cache: &ReleaseCache,█release_keys: &Path,█exe_path: &Path,█broker_name: &str,█) -> Result<ApplyStagedOutcome, String> {█let (version, product_version, artifact) = match prepare_apply(cache, release_keys)? {█Prepared::Done(outcome) => return Ok(outcome),█Prepared::Proceed {█version,█product_version,█artifact,█} => (version, product_version, artifact),█};█// Connect to the broker BEFORE the binary moves - a dead broker fails the█// apply with the seat untouched. The connection is the channel the planned█// brain-restart trigger rides after the swap (ADR-0018 D3-3); it does not█// attach to any session (the broker holds every hosted endpoint across the█// brain cycle - that is the whole point).█let mut brain = connect_retry(broker_name).map_err(|e| format!(\"broker connect: {e}\"))?;█swap_and_record(cache, exe_path, version, &artifact)?;█// RE [...3072ch elided...] o hosted endpoints come back). The█/// recorded `AppliedPending` promotes on that fresh brain's `ready`, exactly as█/// the handoff path's does. Runs the identical verify/classify gate as█/// [`apply_staged`], so every fail-closed refusal is preserved.█// [impl->REQ-UPDATE-ONE-SHOT-FINISH]█// [impl->REQ-UPD-4]█pub fn apply_staged_daemonless(█cache: &ReleaseCache,█release_keys: &Path,█exe_path: &Path,█) -> Result<ApplyStagedOutcome, String> {█let (version, product_version, artifact) = match prepare_apply(cache, release_keys)? {█Prepared::Done(outcome) => return Ok(outcome),█Prepared::Proceed {█version,█product_version,█...█} => (version, product_version, artifact),█};█...█}█[Showing lines 299-393 of 1183. Use :394 to continue]\u000f█</out>█# Tool call ¶█//Finding compatibility delays█grep(pattern=\"N-1|older broker|predates|fallback|backoff|Duration::from_secs\\\\((15|20|30)\\\\)|BRAIN_WRITE_DEADLINE|CONTROLLER_WRITE_DEADLINE|recv_timeout\", path=\"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/src/brain.rs; crates/spt-daemon/src/broker.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt/tests/daemon_refresh_e2e.rs; docs/adr/0038-broker-stream-lifecycle-truth.md; traceable-reqs.toml\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# traceable-reqs.toml█33|id = \"REQ-ARCH-3\"█*34|title = \"Wire-protocol version independent of crate semver, N-1 compat window\"█35|required_stages = [\"impl\", \"unit\"] # activated M0: spt-proto wire version + compat window (T6)█36|[[requirements]]█37|id = \"REQ-ARCH-4\"█...█42|[[requirements]]█*43|id = \"REQ-DAEMON-1\"█44|title = \"One per-machine spt-daemon owning all per-machine state\"█45|required_stages = [\"impl\", \"unit\", \"int\"] # int activated M3b-B9: the daemon E2E (spawn->Psyche-loop->commune->brain-restart-survives->graceful-signoff) drives the consolidated daemon-hosted lifecycle through the real IPC█46|[[requirements]]█...█63|title = \"spt-store::BranchStore (git branch as versioned KV; commit=checkpoint/tip=resume, atomic multi-key, merge-native sync) is the substrate for coarse/durable/audited state (context, registry snapshot+distribution, daemon checkpoint); hot paths (B5 fsync journal) + indexed queries (SQLite spool) excluded (ADR-0011)\"█*64|required_stages = [\"impl\", \"unit\"] # activated M4-D6a: spt_store::branchstore::BranchStore - bare seed repo + linked worktrees (relative-path config, repair fallback), ensure_branch = parentless empty-tree seed (independent KV root [...32431ch elided...] output_bounded(cmd, Duration::from_secs(30))█190| };█191| let refresh_stdout = String::from_utf8_lossy(&refresh.stdout).to_string();█192| let refresh_stderr = String::from_utf8_lossy(&refresh.stderr).to_string();█...█195| // generation (no binary swapped anywhere). --█*196| let cycled = wait_ready_not(&ready_path, Some(brain_pid_before), Duration::from_secs(30));█197|█198| // The broker process must be the SAME OS process (never restarted).█199| let broker_still_running = broker.try_wait().map(|st| st.is_none()).unwrap_or(false);█...█239| .env(\"SPT_HOME\", home.path());█*240| output_bounded(cmd, Duration::from_secs(20))█241| };█242| if let Some((pid, _)) = cycled {█243| kill_pid(pid);█[Some lines truncated to 512 chars]\u000f█</out>█# Assistant ¶█_**Clarifying release tag commit**█**Analyzing cross-version broker compatibility issues**█**Evaluating opener eviction and attach risks**█**Analyzing fallback stall causes**█**Investigating 20-30 second stall timing**_█# Tool call ¶█//Reading requirement release entries█read(path=\"traceable-reqs.toml\", selector=\"2390-2452\")█<out>█\u000e[[requirements]]█...█[[requirements]]█id = \"REQ-UPDATE-RESTART-SAFE-SWAP\"█title = \"THE-FORKENING W4 (operator-grilled 2026-07-14; RETIRES findings-backlog seed #12 REQ-UPDATE-ONE-STEP-SAFE-SWAP): `spt update --restart` = the one-step ergonomic path to the SAFE full-cycle swap - fetch -> `update adapters` -> `apply --finish` LAST (lethal-leg-last, ruled: apply --finish restarts the whole daemon incl. broker/PTYs, so it must be the final act - everything completes from ANY invoking context including an spt-hosted session whose PTY dies at that step; accepted cost: a finish FAILURE leaves updated adapters on old-activated core briefly - loud + operator-attended by nature of the flag). Composes with `-c/--core-only` (skip adapters leg). The 0.28.0 wedge lesson closes: the ergonomic one-step no longer picks the riskier path by default ...█required_stages = [\"doc\", \"impl\", \"unit\"] # FLIPPED in the W4 build commit (todlando 2026-07-15).█[[requirements]]█id = \"REQ-DAEMON-REFRESH\"█title = \"THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` - restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> r [...12394ch elided...] 26-07-10 - HFENDULEAM+ENLYZEAM symmetric green-status sequester): a node holding a valid RosterEntry.address for a peer is NEVER route-less - a failed dial must not delete the only bootstrap route, and recovery must never require an already-successful connection or operator state surgery. Today: resolve_submit_addr = exact cache else id-only (never roster), PRESENCE_DIAL_FAILED unconditionally drop_seed's the cache row, and the cache refills only after a successful seed-proof exchange - one transient + stalled discovery = self-sustaining isolation, invisible (net_up true, heartbeat fresh, durable counts normal). Gate: int - production-path regression at the REAL pump resolver/failure-lifecycle ...█...█[Showing lines 2389-2455 of 2472. Use :2456 to continue. Some lines truncated to 768 chars]\u000f█</out>█# Tool call ¶█//Reading lifecycle hazard timing█read(path=\"docs/KNOWN-HAZARDS.md\", selector=\"620-662,676-716\")█<out>█\u000e## 7. Boundary & delivery integrity (added 2026-05-31 - Stage A red-team)█...█### 7.35 The cached ceremony-clock NTP offset must NOT survive an OS clock STEP - an offset measured against the pre-step clock strands every pairing for the TTL `[REQ-HAZARD-CEREMONY-CLOCK-STEP]`█- **Failure (paid-for, field evidence - the enlyzeam BIGNET-join RCA 2026-07-06):** enlyzeam's `w32time` was STOPPED, so its system clock drifted +3m30s; the pairing ceremony offset (REQ-PAIR-8, `ntp.rs`) correctly corrected the TOTP clock - until an operator `w32tm /resync` STEPPED the OS clock back -210s under the LIVE daemon. The cached offset had been measured against the OLD (drifted) clock and its snapshot was monotonic-only (`when: Option<Instant>`), so the step was invisible to the cache: for up to the 15-min TTL the ceremony clock stayed ~7 TOTP steps in the past, every `subnet join` returned NO_SEED_HOLDER while fresh-process probes met in <500ms, and only a daemon bounce (which forced a fresh query) healed it. Timeline-proven: refresh cadence 15:0...█- **Invariant:** the offset cache MUST detect an OS clock step and re-query, never apply a stale offset across it. The snapshot stores an `(Instant, SystemT [...20269ch elided...] LESS perch's orphan detached Psyche via the cmdline-scoped guard - the handle-reap (`stop_host`, REQ-HAZARD-UNHOST-PSYCHE-REAP) CANNOT (the owning brain is dead, so its `psyche_child` handle died with it) and the brain-START sweep (REQ-HAZARD-BRAIN-RESTART-PSYCHE-DUP) never fires for a perch being STOPPED rather than re-hosted. So the live-host runs the scoped reap after `stop_host` at the reconcile stop-side AND in `confirm_residency_or_unhost`. Fail-safe-decline (pid-alive AND basename==psyche-program AND cmdline contains `<id>-psyche`; any unreadable signal DECLINES - a missed dup is bounded, a wrong-kill catastrophic). The orphan-leak half of perri F-010xF-015 (the psyche own-copy is the other half, ADR-...█...█<!-- [doc->REQ-HAZARD-STORE-INIT-RACE] -->█[Some lines truncated to 768 chars]\u000f█</out>█# Tool call ¶█//Finding brain promotion timing█grep(pattern=\"TRIAL|trial.*Duration|READY|DRAIN|15s|15 s|30s|30 s|old_gen|promotion\", path=\"crates/spt-daemon/src/brainproc.rs; docs; traceable-reqs.toml\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/src/█## brainproc.rs█9|//! `spt daemon brain` child. The child connects over the existing broker socket█*10|//! IPC, signals `ready`, hosts the brain-side logic loops, and idles. D2█11|//! migrates the broker-resident loops here one per commit; **D2-1 moved the net█12|//! consumers** (inbound dispatcher + outbound peer pump - pure IPC clients, no█13|//! shared `Arc<Broker>`) into the brain, gated on the existing `net-status`█...█33|use crate::brain::{now_ms, Brain};█*34|use crate::endpoint::{brain_ready_path, broker_socket_name};█35|use crate::relcache::{AppliedRecord, ReleaseCache};█36|█37|/// Supervisor backoff floor after a brain death (mirrors the peer-pump█...█44|/// A run at least this long counts as healthy and resets the backoff floor.█*45|/// Reused as the per-attempt **trial window** (ADR-0018 Q7, D6-2): a candidate█*46|/// brain has this long to signal `ready` for its generation before the window█*47|/// elapses (the \"up but useless\" alive-never-ready rollback path).█48|const SUPERVISE_HEALTHY_RUN: Duration = Duration::from_secs(30);█*49|/// The readiness-trial retry budget (ADR-0018 Q7, D6-2): a candidate that█*50|/// **exits before** signa [...48846ch elided...] 4|**Status:** RCA-first, **report before fixing** (doyle-dispatched 2026-07-09).█*5|**REQ:** `REQ-SEND-WINDOW-DRAIN-HONOR` (registered, `required_stages = []` - activate on doyle's fix ruling).█6|**Investigator:** todlando. **Off:** `origin/main@2c05dc9` (v0.30.2).█7|█8|---█...█20|█*21|For an **spt-hosted, relay-less** endpoint (the class the idle-edge drain targets),█22|such a row is delivered through the translation binary at the idle window - the█23|exact delivery the tag forbids.█24|█...█42|█*43|### ACTIVE drain - honors the axis (correct)█44|█*45|`spool::drain_active_window_inner`:█46|█47|```█48|include_deferred ? \"WHERE delivered=0 AND \\\"window\\\" != 'idle_only'\"█...█53|█[Showing lines 1-493 of 767 (50.0KB limit). Use :494 to continue. Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Reading Redispatch test harness setup█read(path=\"crates/spt-daemon/tests/redispatch.rs\", selector=\"1-200\")█<out>█\u000e//! REDISPATCH-TRUTH W1 - the three production-path regressions (ADR-0038 D,█//! KNOWN-HAZARDS 7.41): every leg drives the REAL█//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child -█//! a dispatcher GENERATION whose death drops its workers' broker conns█//! exactly like a production brain death). NO manual re-serve anywhere: the█//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)█//! hand-stepped a known stream and bypassed exactly the rediscovery/█//! classification seam that was broken in the field.█//!█//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same█//! endpoint/origin; restart the dispatcher generation only -> the█//! historical stream neither takes nor clears the current controller█//! (post-restart input still renders; no EOF; no replay-storm log line;█//! the retired row left the broker's enumeration).█//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;█//! restart the dispatcher -> durable classification (the pinned opener)█//! resumes the SAME operator stream.█//! - D3: ONE injected transient worker-start failure -> the claim requeues█//! and the retry serves the stream (no perman [...5312ch elided...] } if sid == stream_id => {█for rec in decoder.push(&bytes) {█if let AttachRecord::Output { seq, data_b64 } = rec {█if seq < *cursor {█continue; // replay duplicate - dedup at the render cursor█}█*cursor = seq + 1;█buf.extend_from_slice(█&spt_daemon::msg::decode_bytes(&data_b64).expect(\"b64\"),█);█}█}█}█BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {█saw_eof = true;█}█_ => {}█}█}█saw_eof█}█[Showing lines 1-203 of 621. Use :204 to continue]\u000f█</out>█# Tool call ¶█//Reading opener IPC behavior█read(path=\"crates/spt-daemon/src/brain.rs\", selector=\"1438-1505\")█<out>█\u000e pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {█...█loop {█match self.read_event()? {█BrokerEvent::NetStreams(ev) => return Ok(ev),█BrokerEvent::Error { message } => return Err(io::Error::other(message)),█_ => continue,█}█}█}█/// Retire a completed stream row from redispatch eligibility (ADR-0038 A).█/// The dispatcher calls this when a serve loop runs to completion, so no█/// future dispatcher generation rediscovers and replays the stream█/// (KNOWN-HAZARDS 7.41). Best-effort at the caller: an older broker█/// answers `error` (KH-2.3 N-1 window - retirement then waits for the█/// broker to cycle onto the new binary).█// [impl->REQ-REDISPATCH-FINISHED-RETIRE]█pub fn net_stream_retire(&mut self, stream_id: u64) -> io::Result<bool> {█self.send(█KIND_NET_STREAM_RETIRE,█serde_json::to_value(NetStreamRetireReq { stream_id })█.expect(\"NetStreamRetireReq serializes\"),█)?;█loop {█match self.read_event()? {█BrokerEvent::Other(env) if env.kind == KIND_NET_STREAM [...1655ch elided...] (|e| io::Error::new(io::ErrorKind::InvalidData, e)),█None => Ok(None),█};█}█BrokerEvent::Error { message } => return Err(io::Error::other(message)),█_ => continue,█}█}█}█/// Trigger a **planned brain-process restart** on the broker (ADR-0018 D3-3,█/// Q3): the broker signals its supervisor to cycle the brain child onto the█/// freshly-swapped binary, with `start-reason=update`. `spt update apply`█/// calls this after swapping the binary on disk - the seam that makes a█/// routine update actually run new code without a manual bounce, every█/// broker-held endpoint untouched at the process level.█[Showing lines 1437-1508 of 2236. Use :1509 to continue]\u000f█</out>█# Tool call ¶█//Reading stream retirement implementation█read(path=\"crates/spt-daemon/src/nethost.rs\", selector=\"120-282,535-558,1548-1605\")█<out>█\u000econst OPENER_PIN_MAX: usize = 64 * 1024;█/// Restart-durable opener capture (ADR-0038 B, REQ-STREAM-OPENER-DURABLE):█/// the stream's complete first NDJSON line, pinned OUTSIDE the evictable data█/// ring and held until the stream row goes away. Recovery classifies from█/// this fact, never from ring seq 0 (which a bounded ring evicts under█/// traffic - the abandoned-active-stream leg of the hertz RCA).█enum OpenerCapture {█/// Still hunting the first `\\n` - holds every byte seen so far.█Buffering(Vec<u8>),█/// The complete first line (newline excluded), immutable from here on.█Pinned(Vec<u8>),█/// No newline within [`OPENER_PIN_MAX`] - not an NDJSON opener; give up█/// bounded (classification reads this as \"no opener\").█Oversize,█}█/// Transport backing a held connection. A real `Quic` connection (the only█/// kind an inbound handshake can produce), or a broker-minted `Loopback` -█/// an in-process connection with NO iroh handshake whose identity IS this node,█/// established BY CONSTRUCTION. A loopback conn can ONLY be minted broker-side█/// via [`NetHost::dial_loopback`]; it can never arise from an inbound handshake█/// (a remote always presents it [...10110ch elided...] y = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);█// A retired row answers no-such-stream: classification exists only to█// serve, and a retired exchange must never be served again.█if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {█return None;█}█let line = entry.log.lock().unwrap().opener_line();█Some(line)█}█/// Mark a held stream **retentive** with backpressure `cap` chunks (M11-W3,█/// REQ-SHELL-4): its read log stops evicting (no byte loss) and its read pump█/// lags the sender once the undrained ring reaches `cap` (no unbounded growth).█/// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.█/// No-op if the stream is gone.█// [impl->REQ-SHELL-4]\u000f█</out>█# Tool call ¶█//Checking mixed-image coverage█grep(pattern=\"older broker|old broker|N-1|mixed|v0\\\\.33|predates|unknown command|net_stream_opener|net_stream_retire\", path=\"crates/spt-daemon/tests; crates/spt/tests; crates/spt-daemon/src\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/█## spt-daemon/tests/█### attach.rs█288|█*289|// [int->REQ-HOST-RUN-1] [int->REQ-RC-1] spt-hosted harness bringup + cross-node█290|// attach, end to end: `harnesshost::launch_harness_brokered_in` spawns a harness's█291|// `[session.self]` into broker A's PTY (the spt-hosted bringup - the daemon owns█292|// the PTY, not an external launcher), then an operator on broker B attaches█### daemon_e2e.rs█10|//! 2. **Psyche-loop** - a [`BrainLifecycle`] (the consolidated re-host of the█*11|//! interim `api listen` live half, [`REQ-DAEMON-1`]) spawns the Psyche companion;█12|//! the daemon marks the hosted perch `online`, so its liveness is the daemon█13|//! `status`, never the summarizer pid ([`REQ-HAZARD-DAEMON-HOSTED-LIVENESS`]).█14|//! 3. **commune** - a commune drop lands in the watched dir and a scheduled pulse█*15|//! tick folds it in (the daemon-hosted lifecycle loop, [`REQ-DAEMON-1`]).█16|//! 4. **brain-restart-survives** - the brain is dropped (a crash) and handed off█17|//! repeatedly; across each handoff the child's pid is unchanged, exactly one█18|//! session is hosted, the output stream replays gaplessly, and `gen_start`█...█31|//! must n [...12681ch elided...] broker restart.█25|//!█26|//! ## Why the wedge is established AFTER the candidate boots (the resume paradox)█### worker_lifecycle_e2e.rs█5|//! Proves:█*6|//! 1. N-1 - worker-start MINTS `{parent}-w{N}` and emits the BARE id ALONE on█7|//! STDOUT (the machine-readable result the adapter reads - nothing else on█8|//! stdout); the human `WORKER_STARTED:` line rides STDERR.█*9|//! 2. N-1 - the per-parent counter is monotonic (a second start -> `-w2`).█10|//! 3. W-2 - worker-stop authenticates by the PARENT's `session_id` (the adapter's█11|//! natural emission `worker-stop <id> --session-id <parent sid>`), which the OLD█12|//! empty-sid + token-only record refused 100% (the leak root).█Showing files 1-20 of 47+. Use skip=20 for the next page, or narrow paths/pattern.\u000f█</out>█# Assistant ¶█_**Analyzing broker image refresh behavior**█**Verifying daemon restart behavior inconsistency**_█# Tool call ¶█//Tracing restart semantics█grep(pattern=\"UpdateCmd|Restart|update --restart|cmd_update_restart|apply.*finish|restart_safe|restart_safe_swap|update_restart\", path=\"crates/spt/src; crates/spt/tests; CHANGELOG.md; docs-site; CONTEXT.md\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# CHANGELOG.md█48|█*49|- Updating or refreshing the daemon - `spt update`, `spt update --restart`, or█50| `spt daemon refresh` - no longer disturbs a remote session you are attached to.█51| A daemon cycle could previously replay already-finished output onto a live█52| remote terminal: old text re-typed itself, control of the session was stolen,█...█113|█*114|> **Starting the docs server needs a daemon restart.** The docs server and█*115|> `spt daemon refresh` live in the always-on daemon. `spt update --restart`█*116|> brings them up via a full daemon restart (your live sessions restart as the█117|> daemon comes back). A bare `spt update` swaps the core binary in place and█118|> leaves the running daemon untouched, so the new docs server starts on the█*119|> daemon's next restart.█120|█121|### Added█122|█...█150|█*151|- `spt update --restart` - the one-step full cycle: fetch, update adapters, then█*152| restart the whole daemon onto the new version. Use it when you want█153| everything - core, adapters, and the always-on daemon - brought current in a█*154| single command. Your live sessions restart and come back on their own.█155|█*156|- `spt daemon refresh` - res [...31506ch elided...] and carries the same identity/auth█## instances/█### overview.md█32|Infrastructure endpoints (a gateway the phone treats as always-there) should█*33|not need hands-on bringup after a box reboot or daemon restart. `spt endpoint█34|run ... --save` persists the run - endpoint id, adapter option, and working█35|directory - as a **startup default** in `daemon.json`; the daemon **replays**█36|every saved default when it starts, as a fresh session with the adapter█...█45|This is a **startup default**, not a session restore - it replays what you█*46|saved, never \"whatever was up before the restart\" - and it is symmetric with█47|`spt subnet attach --save` (the serve-state startup default).█48|█49|## Commands█Showing files 1-20 of 24+. Use skip=20 for the next page, or narrow paths/pattern.\u000f█</out>█# Tool call ¶█//Reading update CLI implementation█read(path=\"crates/spt/src/cli.rs\", selector=\"3860-4095\")█<out>█\u000efn cmd_shutdown(id: Option<String>) -> i32 {█...█if id.contains('@') || id.contains(':') {█...█}█// Leg 1: soft-stop - the listener marker goes first so nothing new is█// accepted while the edge runs.█let _ = std::fs::remove_file(perch::resolve_ready_file(&id, ParentHint::Infer));█// Leg 2: the suspend edge (echo gate + shell cascade live in the host).█// Leg-2 stays LOCAL_ONLY (REQ-REST-VERB-ROUTING): shutdown is an endpoint's█// OWN self-suspend - a bare-id miss must NOT route the verb to some other█// node's instance. Pass `false` so the A-3 remote fallback never engages.█cmd_rest(&id, spt_daemon::RestEvent::Suspend, \"SHUTDOWN\", false)█}█/// Run the per-machine daemon (`spt daemon run`, M8 decision 5). FOREGROUND +█/// unelevated by contract (REQ-DAEMON-7): this process IS the daemon, blocking█/// the broker + seed-control channels until signalled. Never auto-detaches -█/// the background/de-elevated path lives in `start`.█///█/// Two guards before binding:█/// - Windows + elevated => REFUSE (exit 2). An elevated `run` would otherwise█/// respawn detached/de-elevated (KH 5.7) and vanish into the background.█/// - A managed service is already [...8761ch elided...] supervisor to cycle its coordinator (an older or unmanaged daemon) - use \\█`spt daemon stop` then `spt daemon start` instead (hosted sessions come back \\█automatically).\"█);█1█}█Err(e) => {█eprintln!(\"DAEMON_REFRESH_FAIL:{e}\");█1█}█}█}█/// The connection + peer-pump status lines (REQ-DAEMON-5/-9). Pure for tests.█/// A net-LESS broker (`net_up == false`) surfaces \"no connection (retrying)\" +█/// a pump waiting on the network - NOT a STALLED alarm read off a stale█/// pre-boot heartbeat (the kitsubito 2026-06-08 boot-race misreport, where the█/// heartbeat age exceeded uptime). With net up, the heartbeat drives█[Showing lines 3859-4098 of 19062. Use :4099 to continue]\u000f█</out>█# Tool call ¶█//Reading changelog update section█read(path=\"CHANGELOG.md\", selector=\"85-170\")█<out>█\u000e## [0.33.0] - 2026-07-16█...█### Changed█...█- **Breaking: `spt whoami` reports only your own identity.** It now prints the█...█- **Listing endpoints is now fast.** `spt endpoint list` (both the human table█...█### Added█- `spt daemon status` now reports the health of the project index that powers the█fast listings - when it was last generated, how many projects it covers, the█last error if any, and repair and stale-read counters. The `--json` output█carries the same detail in a `project_index` block. The index file itself lives█at `$SPT_HOME/index/project-index.json` and is derived state: safe to delete,█and rebuilt by the daemon. A present index file is not by itself proof of█health - the status report is.█### Fixed█- The install instructions now name the real release files -█`spt-x86_64-windows.exe`, `spt-x86_64-linux`, and `spt-x86_64-linux-musl` -█with a per-platform download example and the `chmod +x` step on Linux. The█earlier instructions referenced file names that were never published.█- The `[update.post]` adapter hook is now fully documented: it runs once when an█adapter is first added, runs in the foreground under a 120-second bound,█surfaces its own [...3122ch elided...] every hosted█session.█### Changed█- Bare `spt update` now brings the whole node current: it applies a staged core█update (if any) and then updates your adapters, in that order. When the core█is already current, only the adapters update. Pass `-c` / `--core-only` to█update the core binary alone and skip the adapters step.█- Self-update now fetches releases through the GitHub CLI (`gh`) from a private█release channel instead of a public web address. `gh` is now a prerequisite█for `spt update fetch` and for the `spt install` bootstrap. If `gh` is missing█or not signed in, the update stops with a clear message - how to install `gh`█for your operating system (`winget` / `brew` / your package manager) and to█...█token.█...█[Showing lines 84-173 of 1126. Use :174 to continue]\u000f█</out>█# Tool call ¶█//Finding full restart tests█grep(pattern=\"UPDATE-RESTART-SAFE-SWAP|update --restart|apply --finish|FINISH_ENDPOINT_SURVIVAL|finish.*surviv\", path=\"crates; traceable-reqs.toml; docs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# traceable-reqs.toml█1985|title = \"B-2 (REMOTE-TRUTH triage §B-2, REDUCED @bdc1242): a stale ONLINE+CONTROLLED stamp on a live-session perch self-heals - the info.json driven_by/controlled RECORD is made to match the broker's SINK-TABLE TRUTH. ROOT (persisted-stale-stamp class, doyle Q1): the livehost control reap gates on !has_session (reconcile_hosted_liveness), and a brain-only update KEEPS the session (REQ-UPD-3), so a controller stamp that went stale WHILE the session lived was never re-derived from broker truth. NOT a tr...█*1986|required_stages = [\"impl\", \"unit\", \"int\"] # ACTIVATED REMOTE-TRUTH B-2 REDUCED (todlando); int ACTIVATED on the green [twohost] rig run 28772170556 @279df99 (2026-07-06). impl = OutputLog::reap_dead_controller (broker.rs: drop a controller whose _writer.is_finished() in-memory ONLY, no stamp - lock-safe under the KIND_SESSIONS snapshot log lock) + the snapshot-closure call site (recover_log(&log) made mut; reap BEFORE reading controller_by/has_controller so the reply + the existing off-lock converge_p...█1987|[[requirements]]█1988|id = \"REQ-ADAPTER-FLOOR-ENFORCE\"█1989|title = \"F-5 (REMOTE-TRUTH triage §F-5 + doyle rulings 2026-07-05): BOTH adap [...18802ch elided...] e); both psyche stamps self-clear on█61| next commune post-swap.█62|2. **Release (counter 49 -> v0.29.0)** via deployah (parked - operator wake pending),█## W3-SLICE1-REPORT.md█4|█*5|## REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL - the field-accept unblocker (seed #6) @049b2be█6|█7|Daemon restart no longer massacres hosted endpoints. New one-shot█8|`livehost::resume_restart_orphaned_endpoints` runs at the fresh broker's live-host█## W3-WAVE-GATE-REPORT.md█8||-----|-----|--------|█*9|| REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL | `049b2be` | impl+unit+int |█10|| REQ-DAEMON-STDERR-PERSIST | `32c789e` | impl+unit |█11|| REQ-DAEMON-STOP-LIVE-SESSION-WARN | `ff6ebeb` (+int fix `8931680`) | impl+unit+int |█12|| REQ-RC-RECONNECT-TRUTH | `3d632b9` | impl+unit+int |█[Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Reading N1 compatibility test█read(path=\"crates/spt/tests/n1_pairing.rs\", selector=\"1-190\")█<out>█\u000e//! Restoration D7-2 (ADR-0018 V6) - **new-brain × old-broker** N-1 verb-surface█//! gate: the steady state after every routine brain-only update is a NEW brain█//! talking to an OLDER broker (the broker almost never updates), so the current█//! brain must come up + serve against a prior-era broker over the socket verbs█//! without a clap/arity rejection or a verb desync (KH-2.3 - \"the single most█//! update-frequency-sensitive invariant\").█//!█//! This GROWS the single-binary argv scaffold (`spt-daemon` `n1_compat.rs`, which█//! only proves the current brain *parses* both argv shapes) into a real█//! **cross-binary** pairing: a pinned prior `spt` binary is the broker, the█//! current-tree `spt daemon brain` is the brain.█//!█//! ## The pin (doyle D7 vet, call 4a)█//!█//! The pin is **NOT a pre-restoration release** - a pre-split binary has no brain█//! supervisor (that absence *is* the regression), so it cannot pair at all. The█//! pin is the **restoration split boundary: D1 skeleton `0c95435`** (the first█//! commit where the broker spawns a brain child). D1 also gives the **widest** N-1█//! window: its broker spawns its own brain with the *bare* pre-D3-2 argv (no█//! `--generati [...5905ch elided...] ring(&mut s));█s█};█assert!(█alive,█\"the current brain must stay alive against the old broker (no clap/arity reject, \\█no verb desync) - new-brain × old-broker N-1 steady state.\\nTRACE:\\n{trace}\"█);█// (2) The handshake completed: it connected to the old broker's socket.█assert!(█trace.contains(\"BRAIN_UP\"),█\"the current brain must connect + handshake the old broker (BRAIN_UP).\\nTRACE:\\n{trace}\"█);█// (3) Stable heartbeats: the net-status verb round-trips against the old█// broker every heartbeat - a broker loss would print BRAIN_BROKER_LOST.█assert!(█!trace.contains(\"BRAIN_BROKER_LOST\"),█\"the current brain lost the old broker mid-run - a verb-surface desync.\\nTRACE:\\n{trace}\"█);█}█\u000f█</out>█# Tool call ¶█//Reading session survival contract█read(path=\"CONTEXT.md\", selector=\"560-590\")█<out>█\u000e## Networking█...█### Subnet notifications█...█- **Spool: per-subnet, replicated across that subnet's nodes** (reuses subnet-registry distribution). Each notif is tagged with its subnet; **dismiss-state replicates subnet-wide**, eventually consistent (dismiss on node A clears it on node B). \"Node notif spool\" = each node's local replica. *(Forward seam: dismissal - and the spool key - generalize to **per-(subnet, user)** when the cross-user model lands.)*█...█- **Resurface** re-delivers *undismissed* notifs at boundaries, gated to avoid nagging: skip if already **seen on this endpoint**, and skip if surfaced anywhere within a **global suppression timeout** (default ~1h, configurable) - the timeout is cross-endpoint so a notif can't bounce between endpoints. **Resurface boundaries** (reuse existing reported events, no new plumbing): state->active (`wake` from offline/suspended/dormant), `api boundary clear`, `api boundary compact`, and **new-session-start**. The daemon injects undismissed notifs into the activating/cleared/compacted/fresh session's context.█- **Scope:** an endpoint only ever sees/resurfaces notifs for subnets it is **visible in** (excluded endpoints never receive that subn [...7863ch elided...] se lets a compromised node poison the subnet). spt-core has its own release signing key, distinct from any OS-publisher code-signing (the Windows-publisher-trust question is separate).█**handoff invariant** - **no endpoint process terminates or suspends during a self-update.** We cannot assume every endpoint can safely suspend. Satisfied by the broker/brain split: routine updates replace only the daemon brain, which rehydrates from disk state and re-attaches to the broker's held PTY/socket/child handles. Harness-hosted endpoints (topology 1) are safe by construction; spt-hosted endpoints (topology 2) are kept alive by the broker holding their PTY masters + child processes across the brain restart.█...█[Showing lines 559-593 of 889. Use :594 to continue. Some lines truncated to 768 chars]\u000f█</out>█# Tool call ¶█//Checking release changelog tests█grep(pattern=\"CHANGELOG|release notes|version ladder|contiguous|notes-file|0\\\\.33\\\\.0\", path=\"crates/xtask; .github; docs; tests\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# .github/workflows/█## release.yml█140| cargo run -p xtask -- docs-bundle dist/spt-docs.tar.gz█*141| - name: Build user-facing release notes from CHANGELOG█*142| # The release BODY is the user-facing changelog: the `## [<version>]`█*143| # section of CHANGELOG.md (Added / Changed / Fixed, UX items only - no█144| # internal lingo). Authoring that section before tagging is a required█145| # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the█146| # release loudly rather than shipping an empty/boilerplate body - the█*147| # changelog is not optional.█148| run: |█149| ver=\"${GITHUB_REF_NAME#v}\" # v0.1.2 -> 0.1.2█150| notes=\"dist/RELEASE_NOTES.md\"█...█156| grab { print }█*157| ' CHANGELOG.md > \"$notes\"█158| if ! grep -q '[^[:space:]]' \"$notes\"; then█*159| echo \"::error::CHANGELOG.md has no '## [$ver]' section - add the user-facing changelog for this release before tagging (see docs/RELEASE-RUNBOOK.md).\"█160| exit 1█161| fi█162| echo \"----- release body for ${GITHUB_REF_NAME} -----\"█...█177| --titl [...17932ch elided...] ```█*26|peer chunks 0..125 contiguous, no gap across the brain kill (~seq 58) / restart window█27|post-restart MARKER reached the broker-owned QUIC sender | no duplicate seq█28|A PASS B PASS C PASS D PASS -> OVERALL PASS [OK] (4/4 runs, stable)█29|```█### SPIKE-04-forkpty-parity.md█15|█*16|Invariants (same as Spike #1): **A** child survives brain restart (no PTY EOF) · **B** PTY counter contiguous · **C** input reaches child after brain restart · **D** live client stream contiguous across restart.█17|█18|## Result█19|█20|```█*21|child pid survived | pty ticks 0..39 contiguous, no EOF█22|post-restart input echoed | client 34 ticks, no gap across the kill/restart window█23|A PASS B PASS C PASS D PASS -> OVERALL PASS [OK] (3/3 runs)█24|```█[Some lines truncated to 512 chars]\u000f█</out>█# Assistant ¶█_**Analyzing finish behavior values**_█# Tool call ¶█//Reading composite update flow█read(path=\"crates/spt/src/cli.rs\", selector=\"5038-5135,5215-5295\")█<out>█\u000efn cmd_update_apply(finish: bool) -> i32 {█...█match outcome {█...█Err(e) => {█...█1█}█}█}█/// One leg of the bare `spt update` composite (REQ-UPDATE-DEFAULT-COMPOSITE,█/// REQ-UPDATE-RESTART-SAFE-SWAP).█#[derive(Debug, PartialEq, Eq, Clone, Copy)]█enum UpdateLeg {█/// `update fetch --apply` - stage + install the core in one step (the█/// brain-only cycle: the invoking process survives by construction).█FetchApply,█/// `update fetch` - stage only; the swap waits for the Finish leg.█Fetch,█/// `update adapters` - sweep every gh_release adapter.█Adapters,█/// `update apply --finish` - swap + full daemon restart. LETHAL: from an█/// spt-hosted session the invoker's PTY dies here, so this leg is always█/// planned LAST (everything else has completed by then).█Finish,█}█/// Plan the composite's leg sequence - pure, unit-gated. The invariants:█/// core before adapters (core-first doctrine); `--core-only` drops the█/// adapters leg; `--restart` replaces the in-place apply with stage-now█/// (`Fetch`) + `Finish` LAST (lethal-leg-last, operator-ruled: the whole-daemon█/// restart must be the final act so every other [...6006ch elided...] unning() && std::time::Instant::now() < down_by {█std::thread::sleep(std::time::Duration::from_millis(50));█}█if spt_daemon::is_running() {█eprintln!(█\"DAEMON_STOP_TIMEOUT: the daemon did not stop within 10s - the new version is on \\█disk; run `spt daemon start` once it stops to finish.\"█);█return 1;█}█}█match spt_daemon::start_daemon() {█Ok(_) => {█let (version, product_version, already) = match applied {█O::AppliedDaemonless {█version,█product_version,█}█| O::AppliedPending {█...█} => (version, product_version, false),█...█};█...█}█...█}█}\u000f█</out>█# Tool call ¶█//Reading reconnect contract█read(path=\"crates/spt/src/rc.rs\", selector=\"1560-1660\")█<out>█\u000efn attach_viewport(█...█) -> Result<PumpEnd, String> {█loop {█...█// Final ends (incl. hard errors) exit the loop to the parting copy.█let severed_kind = match &end {█Ok(e) if reconnect_class(e) == EndClass::Reconnect => match e {█PumpEnd::BrokerGone => \"the daemon connection dropped\",█_ => \"the connection was severed\",█},█_ => return end,█};█// Reconnect: full-screen centered banner (operator language - the internal█// detail stays off the terminal per the Q4 UX rule), then re-drive█// establish_attach inside the bounded window. A detach keypress mid-window█// aborts the retry loop honestly. The banner repaints each iteration with a█// live COUNTDOWN (REQ-RC-RECONNECT-TRUTH) so the operator sees rc is bounded█// and working, never the old apparent freeze at a static \"Reconnecting...\".█// establish_attach is CONNECT-ONLY: a down daemon yields DaemonDown here and█// is retried (the operator may bring it back) WITHOUT any WMI resurrection.█let target = remote_node.clone().unwrap_or_else(|| \"local daemon\".to_string());█[...3176ch elided...] he reconnect window, for the banner countdown█/// (REQ-RC-RECONNECT-TRUTH). CEIL so the display reads a full `RECONNECT_WINDOW`s█/// at the start and never flashes `0s` while the loop is still trying (0 only once█/// the window is fully spent, where the caller has already broken to give-up).█// [impl->REQ-RC-RECONNECT-TRUTH]█fn reconnect_remaining_secs(elapsed: Duration) -> u64 {█let left = RECONNECT_WINDOW.saturating_sub(elapsed);█// ceil(left / 1s) via integer millis - 1..=WINDOW while any time remains, 0 at/past.█(left.as_millis() as u64).div_ceil(1000)█}█/// Which pump ends re-drive the viewport (B-3) vs. exit with parting copy.█#[derive(Debug, Clone, Copy, PartialEq, Eq)]█enum EndClass {█Final,█Reconnect,█}█[Showing lines 1559-1663 of 3214. Use :1664 to continue]\u000f█</out>█# Tool call ¶█//Reading restart survival test█read(path=\"crates/spt-daemon/tests/endpoint_survival.rs\", selector=\"1-240\")█<out>█\u000e//! LIFECYCLE-TRUTH W3 (REQ-UPDATE-FINISH-ENDPOINT-SURVIVAL) - daemon-restart endpoint█//! survival, against a REAL broker over the real local IPC.█//!█//! ## The bug this gate locks down (seed #6, the field-accept blocker)█//!█//! A `daemon stop` + `start` (the `update apply` notice's OWN instruction) kills every█//! daemon-hosted harness; pre-W3 they stayed OFFLINE after start (no resurrection)█//! though their records survived (info.json status + adapter + cwd + a ledger row). The█//! fix: `livehost::resume_restart_orphaned_endpoints` runs ONCE at the fresh broker's█//! live-host start and RE-RUNS the harness of every previously-ONLINE spt-hosted█//! endpoint the restart orphaned, resuming from its last ledger session.█//!█//! ## What this rig proves█//!█//! A fresh broker (an empty session table - exactly the state after a `daemon stop`)█//! plus a seeded previously-ONLINE, controllable (spt-hosted), relay-less, dead-custody█//! endpoint with a ledger row -> the pass RE-SPAWNS its harness into a live broker PTY█//! session. RED-first: WITHOUT the pass the broker has no session for it (it stays█//! offline). The two belts are unit-proven in `livehost::restart_resume_gate_*`; this [...5758ch elided...] c::new(),█};█brain█.sessions()█.map(|r| r.sessions.into_iter().map(|s| s.endpoint).collect())█.unwrap_or_default()█}█/// The broker session id hosting the \"survivor\" endpoint, if any (for teardown).█fn survivor_session_id(name: &str) -> Option<u64> {█let mut brain = Brain::cold_start(name, now_ms()).ok()?;█brain█.sessions()█.ok()?█.sessions█.into_iter()█.find(|s| s.endpoint == \"survivor\")█.map(|s| s.session_id)█}█fn kill_pid(pid: u32) {█#[cfg(windows)]█let _ = std::process::Command::new(\"taskkill\")█.args([\"/PID\", &pid.to_string(), \"/F\", \"/T\"])█.output();█#[cfg(unix)]█let _ = std::process::Command::new(\"kill\")█.args([\"-9\", &pid.to_string()])█.output();█}█\u000f█</out>█# Tool call ¶█//Reading brain swap survival test█read(path=\"crates/spt/tests/brain_survive.rs\", selector=\"180-380\")█<out>█\u000efn file_hash(path: &std::path::Path) -> String {█...█let mut h = Sha256::new();█h.update(&bytes);█let digest = h.finalize();█let mut s = String::with_capacity(digest.len() * 2);█for b in digest {█s.push_str(&format!(\"{b:02x}\"));█}█s█}█#[test]█fn pty_and_quic_survive_brain_process_restart_onto_swapped_binary() {█// The in-process broker + brain children both resolve `broker_socket_name()`█// from SPT_HOME, so the supervised child connects to OUR broker. (The█// established daemon-test pattern - daemon_e2e.rs et al. set it the same way.)█let home = tempfile::tempdir().expect(\"tempdir\");█// Single-test binary by design - this is the SOLE `#[test]` here, so it owns█// the process-global SPT_HOME on its own thread (cross-binary tests are█// separate processes). A SECOND `#[test]` added to this file MUST bring the█// env-lock discipline (a shared mutex / serialized home), or it will race this█// one - the D6 env-race lesson (see the spt-daemon test_home pattern).█std::env::set_var(\"SPT_HOME\", home.path());█// -- Two fixtures: A, and B = A + appended padding (the on-disk \"swap\"). --█// Both are the real `s [...7613ch elided...] ack: true,█};█write_frame(█&mut raw,█&Envelope::new(KIND_INPUT, serde_json::to_value(input).expect(\"InputReq serializes\")),█)█.unwrap_or_else(|_| teardown_panic(&stop, \"could not write the marker input frame\"));█}█// The echo child (cat/findstr) echoes the marker -> output appended -> next_seq█// advances. Poll the broker's own log high-water.█let mut advanced = false;█let deadline = Instant::now() + Duration::from_secs(15);█while Instant::now() < deadline {█if broker.session_output_seq(sid).map(|s| s > seq_before).unwrap_or(false) {█advanced = true;█break;█}█thread::sleep(Duration::from_millis(50));█}█...█}█[Showing lines 179-383 of 404. Use :384 to continue]\u000f█</out>█# Tool call ¶█//Finding multi-session refresh tests█grep(pattern=\"refresh|restart|generation\", path=\"crates/spt-daemon/tests; crates/spt/tests\", selector=null, case=false, gitignore=true, skip=20)█<out>█\u000e# crates/█## spt-daemon/tests/█### pumpdeadline.rs█72| io::ErrorKind::TimedOut,█*73| \"the bounded read surfaces TimedOut (the pump's supervised-restart signal)\"█74| );█75| assert!(█76| elapsed >= timeout,█...█96|// 30s carrier deadline (the empirical stall: the update leg burned the full 30s█*97|// -> TimedOut -> whole-round poison -> supervised restart). The sibling of the█98|// carrier-op test above: a carrier `TimedOut` poisons; a peer-reply `TimedOut`█99|// drops ordinarily.█100|#[test]█### redispatch.rs█3|//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child -█*4|//! a dispatcher GENERATION whose death drops its workers' broker conns█5|//! exactly like a production brain death). NO manual re-serve anywhere: the█*6|//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)█7|//! hand-stepped a known stream and bypassed exactly the rediscovery/█8|//! classification seam that was broken in the field.█9|//!█10|//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same█*11|//! endpoint/origin; restart the dispatcher generation only -> the█12|//! historical stream neither takes nor clears the cu [...36669ch elided...] mon2 = DaemonGuard::start(home.path(), &spt, \"b2\");█*698| // Re-anchor the ticker perch on generation 2's pid BEFORE anything█*699| // re-advertises: the old anchor died with generation 1, and a dead-pid row█700| // would replicate Offline to A mid-reheal - unroutable for the re-dial.█701| info::mutate_info(&perch_rc, |rec| {█702| rec.pid = info::PidValue::Numeric(daemon2.broker_pid())█...█712| signal(&rig, &rig.b_hex(), ID_RC, SIG_BYE);█*713| // MUTUAL-HOLD (attempt-4 red): the bye rides THIS generation-2 daemon's█714| // outbound pump, and a fresh daemon's dial to A goes through discovery -█715| // seconds to minutes, not a sleep. Hold the daemon serving until the bye█716| // observably arrived: A ACKs by DISMISSING the row, and the dismissed=true\u000f█</out>█# Tool call ¶█//Finding five-session coverage█grep(pattern=\"for .*0\\\\.\\\\.[45]|0\\\\.\\\\.5|five|5 sessions|spawn.*session.*spawn|multi.session|N.*session\", path=\"crates/spt-daemon/tests; crates/spt/tests\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# crates/spt-daemon/tests/█## access.rs█84|\u000efn wait_for_stream(brain: &mut Brain) -> (u64, String) {█*85| for _ in 0..400 {█86| let reply = brain.net_streams().expect(\"net-streams\");█87| if let Some(s) = reply.streams.first() {█88| return (s.stream_id, s.remote_id_hex.clone());█## attach.rs█9|//! - **remote drive**: keystrokes sent from B arrive at A's PTY through the█*10|//! journaled input path, and the session's output streams back to B as█11|//! sequenced records - B sees its own marker echoed by A's child;█12|//! - **origin is transport truth** (KNOWN-HAZARDS 7.5): the serve loop gates█13|//! on the stream table's handshake-proven `remote_id_hex`;█...█119|fn wait_for_stream(brain: &mut Brain) -> (u64, String) {█*120| for _ in 0..400 {█121| let reply = brain.net_streams().expect(\"net-streams\");█122| if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {█123| return (s.stream_id, s.remote_id_hex.clone());█...█137|/// Given an acquired `conn_id` on `operator` and the `target` broker connection█*138|/// holding `session_id` for `endpoint`: open the attach stream, serve it on█139|/// `target` [...48975ch elided...] es, EndpointInputReq, SpawnReq, Spawned, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT,█*51| KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,█52| KIND_SPAWNED,█53|};█54|use spt_daemon::transport::{send_hello, LocalSocketTransport};█...█108|/// controller_by viewer, the keystroke op, AND the W2 result-gating helpers█*109|/// (`output_contains_within` / `observe_with_xon` / `spawn_xlate_session`). NOT a█110|/// connect deadline: `cold_start_pump`'s socket connect is UNBOUNDED - the `Duration`█111|/// arg is stored as `io_timeout` and applied as `call_deadline = now + io_timeout` to█112|/// every bounded read (`read_event_until`), never to `LocalSocketTransport::connect`█...█[Showing lines 1-1044 of 1211 (50.0KB limit). Use :1045 to continue]\u000f█</out>█# Assistant ¶█_**Analyzing rc/view transport stall timing**█**Investigating 30-second reconnect effect on restarts**█**Confirming release tag and changelog metadata**█**Preparing release notes clarification**_█# Tool call ¶█//Sharing release contract findings█irc(op=\"send\", to=\"Main\", message=\"Release-contract findings: v0.34 tag refs/tags/v0.34.0 = b7b074c; release branch commit eb9fd864 ('release: v0.34.0 counter61'), W1 impl commits ad7fde8/2a110ee/8425357 + ADR pin 571d6a5. Contract mismatch: v0.34 CHANGELOG names `spt update --restart` as nondisturbing, but cli.rs 5054-56/5070ff and [...347ch elided...] fort, so durable opener + physical retirement unavailable until broker full-cycle. No mixed v33 broker/v34 redispatch stream test (n1_pairing only idle heartbeats, zero stream rows). More final soon.\", replyTo=null, await=false, from=null, timeoutMs=null, peek=null)█<out>█\u000eDelivered to 1 peer(s):█- Main: injected\u000f█</out>█# Assistant ¶█_**Investigating release tag discrepancies and latency causes**█**Designing e2e daemon refresh test with PTYs**█**Instrumenting clocks to distinguish ConPTY and transport**█**Planning broker restart for v34 compatibility**█**Inspecting release metadata for broker updates**_█# Tool call ¶█//Tracing broker update classification█grep(pattern=\"BrokerRequired|broker.*required|broker_stale|broker image|UpdateClass|classify\\\\(|brain-only|BrainOnly\", path=\"crates/spt-daemon/src; crates/spt/src; traceable-reqs.toml; CONTEXT.md; docs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# traceable-reqs.toml█413|title = \"No endpoint process terminates/suspends during self-update\"█*414|required_stages = [\"impl\", \"unit\", \"int\"] # int RE-POINTED at restoration D7-1 (ADR-0018 V5) to the PROCESS-level survival E2E (crates/spt/tests/brain_survive.rs: no endpoint terminates across a brain-only update at the PROCESS level - a real brain-child respawn onto a swapped on-disk binary, the PTY child's pid unchanged + held-AND-functional + the QUIC conn intact). Was M3c-C0 brain_swap.rs (the in-process Brain::handoff closure shape - regression-masked per ADR-0018); that tag removed in the same...█415|[[requirements]]█416|id = \"REQ-UPD-4\"█417|title = \"Update gated on user confirmation by default; opt-in full-auto\"█...█495|id = \"REQ-HOST-RUN-2\"█*496|title = \"Project-scoped working directory for spt-hosted bringup: `spt endpoint run` lands the broker-spawned harness PTY in the user's PROJECT cwd, not the daemon's, via an additive `SpawnReq.cwd` field carried through the broker PTY spawn (portable-pty CommandBuilder cwd). N-1-safe wire change (additive, defaulted). Required because the consumer (Claude Code) is project-scoped: broker-inherited cwd = the daemon's cwd = the wro [...46067ch elided...] rs only. `--core-only`/`-c` skips the█*88| adapters leg. Invoker survives by construction (brain-only cycle) - no█89| re-run machinery.█90|- `spt update --restart` = fetch -> **adapters** -> `apply --finish` LAST█91| (lethal-leg-last, ruled: completes from any invoking context; a finish█## W6-GATE-VERDICT.md█29|- Spot-checked DTO field names against source (3 samples, all match): daemon-status█*30| `stall_evict_count/last_ms/pump_heartbeat_ms/broker_stale` (cli.rs:3956-3980),█31| `AdapterVersionJson{adapter,version}` (cli.rs:8641), digest `input_seq`/`after_predates_window`.█32|█33|### 3. SPT_INJECT_VERIFY_ECHO CC-manifest wiring - CLOSED adapter-side (perri) v█Showing files 1-20 of 27+. Use skip=20 for the next page, or narrow paths/pattern.█[Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Reading release classification█read(path=\"crates/spt-daemon/src/update.rs\", selector=\"1-310\")█<out>█\u000e//! The self-update engine (C0) - the update-class taxonomy and the brain-only█//! zero-interruption swap.█//!█//! ADR-0004 §A splits a self-update into **three classes**, each with its own█//! invariant, decided on two axes against the *running* broker:█//!█//! - **brain-only** - the routine case. The running broker can already host the█//! release's brain (the versioned-IPC handshake accepts it) and the broker's█//! held-resource ABI is unchanged, so only the logic swaps. **No endpoint█//! process terminates or suspends** ([`REQ-UPD-3`], absolute for this class) -█//! proven E2E over the M3b handoff substrate.█//! - **broker-compatible** - the release ships a newer broker that the running█//! broker's IPC floor cannot host the new brain against, but the held-resource█//! *type* is unchanged, so the new broker hot-adopts the existing handles█//! behind the versioned IPC; endpoints still survive. (Apply path deferred -█//! no spike yet, ADR-0004 §A.)█//! - **broker-breaking** - the held-resource *type* or an OS PTY/socket API█//! changed; this needs a *planned endpoint-cycle* and MAY suspend endpoints█//! with consent + scheduling. [`REQ-UPD-3`] is explicitly we [...12177ch elided...] below the running█// broker's IPC floor => broker-compatible: the broker binary must move to host█// it, but the held-resource type is unchanged so endpoints still survive.█#[test]█fn classify_broker_compatible_when_brain_below_floor() {█// A running broker with a raised floor (it has already moved past v1).█let running = BrokerAbi {█ipc_version: 3,█min_compatible: 3,█resource_abi: BROKER_RESOURCE_ABI,█};█let old_brain = ReleaseSpec {█brain_ipc_version: 2,█broker_resource_abi: BROKER_RESOURCE_ABI,█};█assert_eq!(█classify(&running, &old_brain),█UpdateClass::BrokerCompatible█);█}█...█}█[Showing lines 1-313 of 529. Use :314 to continue]\u000f█</out>█# Tool call ¶█//Reading image status logic█read(path=\"crates/spt/src/cli.rs\", selector=\"4180-4245\")█<out>█\u000estruct DaemonStatusJson {█...█#[serde(default, skip_serializing_if = \"Option::is_none\")]█project_index: Option<spt_daemon::projwriter::IndexWriterStats>,█/// MESH-RECOVERY (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): the pump's█/// reachability snapshot - live peer count, last successful dial, last█/// admitted registry update, all-peer-failure window, per-peer failing█/// stages. ADDITIVE (an N-1 reader ignores it); `None` = no snapshot on█/// this home yet.█#[serde(default, skip_serializing_if = \"Option::is_none\")]█peer_health: Option<spt_daemon::pump::health::PumpHealth>,█/// The rendered verdict over `peer_health`: `idle` / `healthy` /█/// `connecting` / `degraded`. Omitted with it.█#[serde(default, skip_serializing_if = \"Option::is_none\")]█peer_health_verdict: Option<String>,█}█/// One member subnet's count row in the daemon status view.█// [impl->REQ-CLI-JSON]█#[derive(serde::Serialize)]█struct DaemonSubnetJson {█name: String,█nodes: usize,█endpoints: usize,█}█/// F-025 running-image surface: compose the `daemon status` line reporting the█/// version the BROKER process is actually executing beside the installed C [...1864ch elided...] ility health line (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH):█/// the answer to \"can I reach anyone?\" - the question the 2026-07-10 sequester█/// proved `net_up`/heartbeat/counts all dodge. Degraded NAMES the stage the█/// dials die in; healthy requires a live peer connection (real progress), so█/// the incident fingerprint (all dials failing, everything else green) renders█/// DEGRADED here. `None` = no health snapshot (older daemon / no pump yet) or█/// a solo node with nobody to reach - silent, not alarming. Pure for tests.█// [impl->REQ-PUMP-STAGE-TRUTH]█fn render_peer_health_line(█health: Option<&spt_daemon::pump::health::PumpHealth>,█now: u64,█) -> Option<String> {█use spt_daemon::pump::health::HealthVerdict;█...█}█[Showing lines 4179-4248 of 19062. Use :4249 to continue]\u000f█</out>█# Tool call ¶█//Reading redispatch hazard█read(path=\"docs/KNOWN-HAZARDS.md\", selector=\"735-770\")█<out>█\u000e# Known Hazards█...█## Conformance checklist (condensed)█...█| # | Invariant | spt-core surface |█...█| 7.20 | `spt rc` must forward the SCROLL wheel to the harness - our mouse capture (for right-click paste, 7.18) steals WT's native scroll. Track the harness's mouse-reporting mode from its OUTPUT (DECSET `ESC[?1000/1002/1003h` + `1006h` SGR, and `...l` off; scan survives a split across output chunks) into `MouseMode{enabled,sgr}`; forward `ScrollUp/Down` as an xterm SGR report (`ESC[<64/65;col+1;row+1M`) ONLY when `enabled && sgr`, else drop; Moved/drag/clicks dropped. cfg(windows) only - Unix scrolls natively | `rc.rs` `MouseMode`/`MouseModeScanner`/`scroll_dir`/`scroll_sgr`/`spawn_stdin_reader_events`, unit-only (live mouse = HITL) |█| 7.21 | Exactly ONE LIVE `controller_writer` per brain<->broker connection + every writer emits an ASCENDING seq stream => a snap-above consumer over the surviving writer's COMPLETE `[0,end]` replay delivers `[K,end]` with no skip/dup. A brain-restart re-serve double-registered the controller (handoff's `subscribe(from_seq=K)` + the re-serve's `attach_as(sid,0)`) -> two writers raced one socket -> the consumer's strict reject-gap legacy path saw `got seq 1 want [...7492ch elided...] | broker `conn.rs` `BrokerConn`, all write sites (`controller_writer`/`viewer_writer`/`send_frame`/nethost), int `brain_decouple.rs` |█| 7.31 | The Psyche failure budget counts REAL per-event attempts - every bounded per-event turn feeds a consecutive-N budget (default 3), so per-event churn a resident rate-guard was blind to now counts by construction. Fixes hall-bf's ~12/min re-host churn that ran invisibly (boot records weren't ledger boundaries to the old guard) | `lifecycle.rs` `note_turn_outcome`/`psyche_turn_strikes_exhausted` (stamps `psyche_host_error` on exhaustion, no `status` de-stamp), unit synthetic failure-loop trips the budget (RED-first control) |█<!-- [doc->REQ-HAZARD-THRASH-GUARD-BLIND] -->█<!-- [doc->REQ-HAZARD-STORE-INIT-RACE] -->█[Some lines truncated to 768 chars]\u000f█</out>█# Tool call ¶█//Finding v34 release validation█grep(pattern=\"counter 61|v0\\\\.34\\\\.0|REDISPATCH-TRUTH W1|W1 gate|GATE.*redispatch|redispatch.*PASS\", path=\"docs; .git/logs; CHANGELOG.md\", selector=null, case=false, gitignore=false, skip=null)█<out>█\u000e# docs/█## KNOWN-HAZARDS.md█667|- **Failure (paid-for, hertz field RCA 2026-07-16 - 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply - same `applyhost.rs` path) launches a fresh `dispatch::run_dispatch_loop` whose EMPTY per-process `claimed` set enumerates every broker-held peer stream. `NetShared.streams` has NO removal path (`StreamLog::finish` only marks) and the claim condition has no finished filter -> the fresh dispatcher replays FINISHED historical attach streams. A repl...█*668|- **Invariant:** redispatch eligibility is LIFECYCLE-gated: a finished/terminal stream row is retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach); classification identity is restart-durable and independent of the evictable data ring (immutable opener fact pinned per stream until close); claims are retryable on transient worker-setup failure and terminal on terminal outcomes (no hot-loop). The legitimate same-`by` successor re-take after a brain restart still s...█669|- **spt-core mapping:** `spt-daemon/src/dispatch.rs` `run_dispatch_loop` claim condition + `peek_first_line` classification + worker spaw [...9044ch elided...] 43d83d9 898401c1677c3749ca810b9e67e8e9b7252f6e7c Reavo End <decidel@gmail.com> 1782395914 -0700 commit: docs(v0.16.0): ratify W2 - self_verifies attestation-only + sentinel token█8|898401c1677c3749ca810b9e67e8e9b7252f6e7c a0f89796bbdce1aec071c8d6fb26894c6fa64ce5 Reavo End <decidel@gmail.com> 1782396886 -0700 commit: feat(cli): W3 - remove --reply-to + empty-scope create (v0.16.0)█### release/█#### v0.34.0█1|0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700 branch: Created from 01d2d18█*2|01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700 commit: release: v0.34.0 (counter 61) - REDISPATCH-TRUTH W1█[Some lines truncated to 512 chars]\u000f█</out>","textHead":"# User ¶█Complete the assignment below, thoroughly:█# Target█Release metadata, changelog, release commit, and requirement/ADR changes for v0.34.0 affecting broker image refresh, redispatch, session stream survival, or PTY handling. Non-goal: broad unrelated release review.█# Change█Determine intended v0.34 behavior and whether a known timing/compatibility tradeoff matches the reported symptom. Cross-check docs against implementation and tests.█# Acceptance█Report relevant release changes and missing test coverage that could allow a 20-30 second post-start/sporadic stall regression.█# Assistant ¶█_**Planning coordinate and mapping tasks**_█# Tool call ¶█//Coordinating release scope█irc(op=\"send\", to=\"V034DiffRca\", message=\"I own v0.34 release contract/docs/changelog/ADR cross-check. Please share any commit/diff findings around broker refresh, redispatch, streams, PTY; I'll avoid broad archaeology duplication.\", replyTo=null, await=false, from=null, timeoutMs=null, peek=null)█<out>█\u000eDelivered to 1 peer(s):█- V034DiffRca: injected\u000f█</out>█# Tool call ¶█//Mapping release artifacts█glob(path=\"CHANGELOG*; VERSION*; docs/**/*; .github/**/*; **/*release*; **/*ADR*; **/*adr*\", hidden=true, gitignore=true, limit=500)█<out>█\u000eCHANGELOG.md█# docs/█MESH-RECOVERY-TRIAGE.md█KNOWN-HAZARDS.md█RELEASE-RUNBOOK.md█REDISPATCH-TRUTH-TRIAGE.md█STORAGE.md█PROJECT-INDEX-TRIAGE.md█THE-FORKENING-TRIAGE.md█UPDATE-WEDGE-2-ROUND3-CODEREAD.md█UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md█PUMP-W2-DESIGN.md█PUMP-W2-GATE-HANDOFF.md█PUMP-W2-BUILD-DELTA.md█PUMP-W2-BUILD-GO.md█MSG-IDENTITY-DISPATCH.md█GATEWAY-LIVENESS-DISPATCH.md█GATEWAY-RCA-STEP1.md█F-035-RCA.md█F-035-RELAY-RCA.md█F-035-CONFLICT.md█PUMP-TRUTH-TRIAGE.md█PUMP-W2-DISPATCH-RULING.md█MUSL-RELEASE-HANDOFF.md█MUSL-W3-DISPATCH-RULING.md█MUSL-W1-DISPATCH-RULING.md█NEXT-MILESTONE-MUSL-TIER-TRIAGE.md█W6-GATE-VERDICT.md█W6-DISPATCH-RULING.md█W5-WAVE-GATE-REPORT.md█W5-GATE-VERDICT.md█W5-GATE-HOLD.md█W5-DISPATCH-RULING.md█W4-WAVE-GATE-REPORT.md█W4-GATE-VERDICT.md█W4-DISPATCH-RULING.md█W3-WAVE-GATE-REPORT.md█W3-GATE-VERDICT.md█W3-SLICE1-REPORT.md█W2-GATE-VERDICT.md█W3-DIGEST-GENERATION-UNION-RULING.md█W3-ENDPOINT-SURVIVAL-DESIGN.md█W2-DESIGN-RULING.md█W2-BUILT-REPORT.md█W2-DESIGN-PROPOSAL.md█NEXT-MILESTONE-LIFECYCLETRUTH-TRIAGE.md█INJECT-SETTLE-REARM-GATE-VERDICT.md█NEXT-MILESTONE-WORKERTRUTH-TRIAGE.md█FLAKE-LEDGER.md█TWO-HOST-RUNBOOK.md█NEXT-MILESTONE-REMOTETRUTH-TRIAGE.md█MANIFEST.m [...1809ch elided...] not-rehome.md█0011-branchstore-git-kv-state-substrate.md█0006-multi-subnet-membership-model.md█0007-subnet-notification-primitive.md█0003-multi-instance-node-anchored-identity-model.md█0004-single-daemon-broker-brain-split-and-self-update.md█0002-bake-networking-into-core-collapse-spt-node.md█0001-clean-room-fork-from-claude-skill-owl.md█## design/█subnet-presence-display.md█robust-wan-subnet-join.md█v0.16.0-update-arc-and-cli.md█## spikes/█SPIKE-06-idempotent-boundary.md█SPIKE-07-branchstore.md█SPIKE-04-forkpty-parity.md█SPIKE-05-restart-stress.md█SPIKE-03-quic-survival.md█SPIKE-02-iroh-smoke.md█SPIKE-01-broker-handoff.md█## reviews/█STAGE-A-codex-redteam.md█# .github/workflows/█ci.yml█release.yml█# crates/█## spt-daemon/src/█release.rs█## spt/tests/█release_verify_e2e.rs█# releases-repo/\u000f█</out>█# Tool call ¶█//Finding version references█grep(pattern=\"0\\\\.34\\\\.0|v0\\\\.34|0\\\\.33\\\\.0|v0\\\\.33\", path=\".\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# CHANGELOG.md█42|█*43|## [0.34.0] - 2026-07-16█44|█45|A stability fix for live remote sessions during a daemon update.█46|█...█56|█*57|## [0.33.0] - 2026-07-16█58|█59|A performance and identity release. The commands that list endpoints and choose█60|a run now answer in a fraction of a second instead of many seconds, `spt whoami`█# docs/█## REDISPATCH-TRUTH-TRIAGE.md█2|█*3|> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first█4|> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:█5|> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`█6|> (hertz source-level, doyle seam-verified same day). Decision record:█...█41|2/3): 4/5 endpoints frozen across both 2026-07-16 doyle refreshes█*42|(deployah-recovery + the v0.33.0 apply) + lia on the update;█43|`DISPATCH:N:Failed` lines with low/historical stream ids in fresh█44|generations = the indiscriminate-replay supporting evidence.█45|█## RELEASE-RUNBOOK.md█52| with no gap. Inserting the new heading with an editor whose anchor spans the█*53| previous heading can silently *consume* that heading (v0.34.0 did this to█*54| `## [0.33.0]`), folding the prior rel [...2331ch elided...] ing, pid, net_up, pump_heartbeat_ms, managed_by, managed_active, subnets[], local_endpoints[], broker_image?, broker_stale?, stall_evict_count?, stall_evict_last_ms?, project_index? }` - `project_index` *(since v0.33.0)* is the index writer's health block: `{generated_ms, source_generation, pending_refresh, last_run_ms, last_duration_ms, last_error?, endpoints, projects, cwds, cwd_cache_hits, cwd_cache_misses, stale_reads, repairs, last_cycle:{branch_enumerations,tree_scans,d...█104|| `subnet status [--nodes]` | `{ daemon_running, subnets[] }` - each `{name, node_count, endpoint_count, nodes[]}` |█105|| `subnet show-code` | `{ subnet, code, otpauth_uri? }` |█106|| `notif list` | `{ notifs[] }` - each `{notif_id, subnet, kind, state, from_id, head}` |█[Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Finding refresh contracts█grep(pattern=\"image refresh|refresh image|broker image|refresh.*broker|broker.*refresh|redispatch|re-dispatch\", path=\".\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# CONTEXT.md█387|█*388|**redispatch** (REDISPATCH-TRUTH, ADR-0038): the fresh brain's reconstruction of target-side attach workers over broker-held peer streams after a **brain cycle** (`spt daemon refresh` / `spt update` apply - the PTYs and QUIC streams survive; the brain-owned serve workers do not). Redispatch eligibility is **lifecycle-gated**: finished/terminal stream rows are retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach - the KNOWN-HAZARDS 7.41 frozen-PTY steal/clear...█*389|_Avoid_: conflating redispatch with msg dispatch or session-cursor resume; filtering replay by origin identity (the legitimate same-`by` successor re-take must keep working - lifecycle is the discriminator).█390|█391|<!-- [doc->REQ-RC-KEY-VT-TRANSLATE] -->█392|**rc keyboard input (Windows VT translation, v0.13.0 bug 2).** On **Windows** an interactive `spt rc` console reads crossterm **key events** and translates each to **standard xterm VT** (`translate_key_event`) - arrows / Home / End / PgUp/Dn / Insert / Delete / F-keys + modifiers all reach the harness as the universal terminal contract (**agnostic**, NOT win32-input-mode; the legacy console deli [...19621ch elided...] with `spt daemon stop` then `spt daemon start`.\"█4219| ),█4220| None => format!(█*4221| \"broker image: not reported (the running daemon predates version reporting, or could \\█4222| not be queried) - if it is older than the installed {installed}, restart with \\█4223| `spt daemon stop` then `spt daemon start` to load {installed}.\"█4224| ),█### tests/█#### daemon_refresh_e2e.rs█282| broker_still_running,█*283| \"REQ-DAEMON-REFRESH: the broker process must survive the refresh untouched \\█284| (pid {broker_pid}).\\n=== brain stderr ===\\n{brain_stderr}\"█285| );█286| assert!(█Showing files 1-20 of 28. Use skip=20 for the next page, or narrow paths/pattern.█[Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Finding stream PTY contracts█grep(pattern=\"stream survival|surviv.*stream|session stream|ConPTY|PTY|pseudo.?terminal|20.?30|30.?second|timeout.*30|30000|25_?000|20_?000\", path=\"docs; CHANGELOG.md; README.md; src; packages; test; tests\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# docs/█## BROKER-BRAIN-SPLIT-RESTORATION.md█50|█*51|- **broker** (stable kernel) - holds *only* the un-transferable, must-not-die resources: PTY master fds, spawned harness child processes, listening network sockets, and (per the §B ownership table) the Iroh/QUIC endpoint + conn table. Minimal, versioned local IPC. Almost never updates.█52|- **daemon brain** (userspace) - all logic (routing, registry, pulse/psyche loops, manifest parsing, update orchestration). Restarts freely on update; rehydrates from disk and re-attaches to broker-held handles.█53|█54|Explicit evidence the split was meant to be a **process** boundary:█...█57|- `M3a-PLAN.md`, `M3-PLAN.md`, `M3b-PLAN.md` all name \"the broker **process** + IPC (M3b).\"█*58|- Spikes 01/03/04/05/06 each **PROVEN** with *two separate binaries* (`spt-spikes/spike-01-broker-handoff`): a PTY child + a live QUIC transfer survive 100× brain restarts, gapless and exactly-once. A spike that proves \"survives a brain restart\" presupposes the broker is a separate process.█59|█60|### 2.2 What production actually does█61|█...█72|█*73|- The brain cannot restart onto a new binary without killing the in-process broker thread - which would c [...48849ch elided...] 5 controller/viewer model (doyle ruling 2026-06-14, Q1).█## MANIFEST.md█129|<!-- [doc->REQ-PSYCHE-CONTEXT-FILE-INDIRECTION] -->█*130|**Psyche-download - `{psyche_context_file}` (file-always, replaces `{psyche_context}`).** The composed Psyche mind rides a **file**, never the command argv: before each turn spt-core writes the mind into the nested psyche perch dir and fills a single **`{psyche_context_file}` = that path** (argv-cap-immune - a real ~20 KB mind exceeds the win32 command-line cap and would brick the spawn). The soft **fresh-vs-continue** discriminator is the file's **content**, not key presence: a **fresh** (first / re...█131|█132|<!-- [doc->REQ-PSYCHE-NESTED-RESOLUTION] -->█[Showing lines 1-276 of 531 (49.9KB limit). Use :277 to continue. Some lines truncated to 512 chars]\u000f█</out>█# Assistant ¶█_**Tracing release commit via git metadata**_█# Tool call ¶█//Reading release notes█read(path=\"CHANGELOG.md\", selector=\"35-85\")█<out>█\u000e## [0.35.0] - 2026-07-16█...█### Fixed█...█- **A node no longer strands itself from its subnets after a brief failure to█...█Routes are now kept and marked unreachable rather than deleted, and address█resolution falls back to the roster, so the node recovers on its own with no█manual state surgery.█- **Poisoned peer-address entries are repaired at startup.** If a cached peer█address has come to claim a different peer's identity, the daemon now detects█and repairs it from the subnet roster when it starts - and says so loudly in█the log - rather than carrying the bad entry forward.█## [0.34.0] - 2026-07-16█A stability fix for live remote sessions during a daemon update.█### Fixed█- Updating or refreshing the daemon - `spt update`, `spt update --restart`, or█`spt daemon refresh` - no longer disturbs a remote session you are attached to.█A daemon cycle could previously replay already-finished output onto a live█remote terminal: old text re-typed itself, control of the session was stolen,█or a running session was left frozen. The daemon now treats a finished█session's history as terminal and never re-drives it onto a live terminal, so█the remote sessions you are a [...1108ch elided...] picker, and `spt api endpoint-info` read a█project index the daemon keeps current, instead of inspecting each project's█git state on every call. On a node with many endpoints this takes these█commands from many seconds to well under a second. Two notes on the trade-off:█a project you just changed may show its previous attribution for a brief moment█(the index refreshes on change and reconciles periodically), and on a node█whose daemon has not yet built the index, attribution shows as `-` until the█first build lands.█### Added█- `spt daemon status` now reports the health of the project index that powers the█fast listings - when it was last generated, how many projects it covers, the█...█health - the status report is.█...█[Showing lines 34-88 of 1126. Use :89 to continue]\u000f█</out>█# Tool call ¶█//Reading redispatch ruling█read(path=\"docs/REDISPATCH-TRUTH-TRIAGE.md\", selector=\"1-180\")█<out>█\u000e# REDISPATCH-TRUTH - milestone triage (JIT)█> Doyle triage 2026-07-16, dispatched at the v0.33.0 boundary (ranked-first█> from the hertz same-day RCA; displaces MESH-RECOVERY). Canonical RCA:█> `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`█> (hertz source-level, doyle seam-verified same day). Decision record:█> ADR-0038. Lane: doyle triages -> todlando builds -> doyle gates -> deployah█> releases -> hertz field-verify (they hold the incident logs).█## Why (field: 4/5 endpoints frozen on one refresh, twice in one day)█`spt daemon refresh` / `spt update` (same brain-cycle path, `applyhost.rs`)█freezes active remote `spt rc` sessions. The PTYs and broker-held QUIC█streams survive by construction - the freeze is a **production redispatch█bug** in the fresh brain's stream reconstruction:█1. **Stale replay steals live controllers.** The fresh█`dispatch::run_dispatch_loop` starts with an EMPTY per-process `claimed`█set (dispatch.rs:206) and its claim condition█(`initiated_locally || next_seq == 0 || !claimed.insert`, dispatch.rs:214)█has NO finished filter - while `NetShared.streams` has NO removal path█(single insert nethost.rs ~649; `StreamLog::finis [...5350ch elided...] eqs check` green.█## Possible kin (NOT folded - verify, don't assume)█- deployah zombie controlled-by latch (REQ-ENDPOINT-CYCLE-HONEST seed,█daemon-lifecycle cluster): plausibly the same stale-stream fingerprint,█but unproven. The seed stands in its own cluster unless W1 evidence█shows the same root; do not widen this milestone on speculation.█- stall-evict 15->17 baseline shift: likely same fingerprint; re-measure at█field-verify, no dedicated work.█## Explicitly out of scope█- MESH-RECOVERY (sibling milestone candidate, separate contract; ","textTail":"\u000efn wait_for_stream(brain: &mut Brain) -> (u64, String) {█*85| for _ in 0..400 {█86| let reply = brain.net_streams().expect(\"net-streams\");█87| if let Some(s) = reply.streams.first() {█88| return (s.stream_id, s.remote_id_hex.clone());█## attach.rs█9|//! - **remote drive**: keystrokes sent from B arrive at A's PTY through the█*10|//! journaled input path, and the session's output streams back to B as█11|//! sequenced records - B sees its own marker echoed by A's child;█12|//! - **origin is transport truth** (KNOWN-HAZARDS 7.5): the serve loop gates█13|//! on the stream table's handshake-proven `remote_id_hex`;█...█119|fn wait_for_stream(brain: &mut Brain) -> (u64, String) {█*120| for _ in 0..400 {█121| let reply = brain.net_streams().expect(\"net-streams\");█122| if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {█123| return (s.stream_id, s.remote_id_hex.clone());█...█137|/// Given an acquired `conn_id` on `operator` and the `target` broker connection█*138|/// holding `session_id` for `endpoint`: open the attach stream, serve it on█139|/// `target` [...48975ch elided...] es, EndpointInputReq, SpawnReq, Spawned, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT,█*51| KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,█52| KIND_SPAWNED,█53|};█54|use spt_daemon::transport::{send_hello, LocalSocketTransport};█...█108|/// controller_by viewer, the keystroke op, AND the W2 result-gating helpers█*109|/// (`output_contains_within` / `observe_with_xon` / `spawn_xlate_session`). NOT a█110|/// connect deadline: `cold_start_pump`'s socket connect is UNBOUNDED - the `Duration`█111|/// arg is stored as `io_timeout` and applied as `call_deadline = now + io_timeout` to█112|/// every bounded read (`read_event_until`), never to `LocalSocketTransport::connect`█...█[Showing lines 1-1044 of 1211 (50.0KB limit). Use :1045 to continue]\u000f█</out>█# Assistant ¶█_**Analyzing rc/view transport stall timing**█**Investigating 30-second reconnect effect on restarts**█**Confirming release tag and changelog metadata**█**Preparing release notes clarification**_█# Tool call ¶█//Sharing release contract findings█irc(op=\"send\", to=\"Main\", message=\"Release-contract findings: v0.34 tag refs/tags/v0.34.0 = b7b074c; release branch commit eb9fd864 ('release: v0.34.0 counter61'), W1 impl commits ad7fde8/2a110ee/8425357 + ADR pin 571d6a5. Contract mismatch: v0.34 CHANGELOG names `spt update --restart` as nondisturbing, but cli.rs 5054-56/5070ff and [...347ch elided...] fort, so durable opener + physical retirement unavailable until broker full-cycle. No mixed v33 broker/v34 redispatch stream test (n1_pairing only idle heartbeats, zero stream rows). More final soon.\", replyTo=null, await=false, from=null, timeoutMs=null, peek=null)█<out>█\u000eDelivered to 1 peer(s):█- Main: injected\u000f█</out>█# Assistant ¶█_**Investigating release tag discrepancies and latency causes**█**Designing e2e daemon refresh test with PTYs**█**Instrumenting clocks to distinguish ConPTY and transport**█**Planning broker restart for v34 compatibility**█**Inspecting release metadata for broker updates**_█# Tool call ¶█//Tracing broker update classification█grep(pattern=\"BrokerRequired|broker.*required|broker_stale|broker image|UpdateClass|classify\\\\(|brain-only|BrainOnly\", path=\"crates/spt-daemon/src; crates/spt/src; traceable-reqs.toml; CONTEXT.md; docs\", selector=null, case=false, gitignore=true, skip=null)█<out>█\u000e# traceable-reqs.toml█413|title = \"No endpoint process terminates/suspends during self-update\"█*414|required_stages = [\"impl\", \"unit\", \"int\"] # int RE-POINTED at restoration D7-1 (ADR-0018 V5) to the PROCESS-level survival E2E (crates/spt/tests/brain_survive.rs: no endpoint terminates across a brain-only update at the PROCESS level - a real brain-child respawn onto a swapped on-disk binary, the PTY child's pid unchanged + held-AND-functional + the QUIC conn intact). Was M3c-C0 brain_swap.rs (the in-process Brain::handoff closure shape - regression-masked per ADR-0018); that tag removed in the same...█415|[[requirements]]█416|id = \"REQ-UPD-4\"█417|title = \"Update gated on user confirmation by default; opt-in full-auto\"█...█495|id = \"REQ-HOST-RUN-2\"█*496|title = \"Project-scoped working directory for spt-hosted bringup: `spt endpoint run` lands the broker-spawned harness PTY in the user's PROJECT cwd, not the daemon's, via an additive `SpawnReq.cwd` field carried through the broker PTY spawn (portable-pty CommandBuilder cwd). N-1-safe wire change (additive, defaulted). Required because the consumer (Claude Code) is project-scoped: broker-inherited cwd = the daemon's cwd = the wro [...46067ch elided...] rs only. `--core-only`/`-c` skips the█*88| adapters leg. Invoker survives by construction (brain-only cycle) - no█89| re-run machinery.█90|- `spt update --restart` = fetch -> **adapters** -> `apply --finish` LAST█91| (lethal-leg-last, ruled: completes from any invoking context; a finish█## W6-GATE-VERDICT.md█29|- Spot-checked DTO field names against source (3 samples, all match): daemon-status█*30| `stall_evict_count/last_ms/pump_heartbeat_ms/broker_stale` (cli.rs:3956-3980),█31| `AdapterVersionJson{adapter,version}` (cli.rs:8641), digest `input_seq`/`after_predates_window`.█32|█33|### 3. SPT_INJECT_VERIFY_ECHO CC-manifest wiring - CLOSED adapter-side (perri) v█Showing files 1-20 of 27+. Use skip=20 for the next page, or narrow paths/pattern.█[Some lines truncated to 512 chars]\u000f█</out>█# Tool call ¶█//Reading release classification█read(path=\"crates/spt-daemon/src/update.rs\", selector=\"1-310\")█<out>█\u000e//! The self-update engine (C0) - the update-class taxonomy and the brain-only█//! zero-interruption swap.█//!█//! ADR-0004 §A splits a self-update into **three classes**, each with its own█//! invariant, decided on two axes against the *running* broker:█//!█//! - **brain-only** - the routine case. The running broker can already host the█//! release's brain (the versioned-IPC handshake accepts it) and the broker's█//! held-resource ABI is unchanged, so only the logic swaps. **No endpoint█//! process terminates or suspends** ([`REQ-UPD-3`], absolute for this class) -█//! proven E2E over the M3b handoff substrate.█//! - **broker-compatible** - the release ships a newer broker that the running█//! broker's IPC floor cannot host the new brain against, but the held-resource█//! *type* is unchanged, so the new broker hot-adopts the existing handles█//! behind the versioned IPC; endpoints still survive. (Apply path deferred -█//! no spike yet, ADR-0004 §A.)█//! - **broker-breaking** - the held-resource *type* or an OS PTY/socket API█//! changed; this needs a *planned endpoint-cycle* and MAY suspend endpoints█//! with consent + scheduling. [`REQ-UPD-3`] is explicitly we [...12177ch elided...] below the running█// broker's IPC floor => broker-compatible: the broker binary must move to host█// it, but the held-resource type is unchanged so endpoints still survive.█#[test]█fn classify_broker_compatible_when_brain_below_floor() {█// A running broker with a raised floor (it has already moved past v1).█let running = BrokerAbi {█ipc_version: 3,█min_compatible: 3,█resource_abi: BROKER_RESOURCE_ABI,█};█let old_brain = ReleaseSpec {█brain_ipc_version: 2,█broker_resource_abi: BROKER_RESOURCE_ABI,█};█assert_eq!(█classify(&running, &old_brain),█UpdateClass::BrokerCompatible█);█}█...█}█[Showing lines 1-313 of 529. Use :314 to continue]\u000f█</out>█# Tool call ¶█//Reading image status logic█read(path=\"crates/spt/src/cli.rs\", selector=\"4180-4245\")█<out>█\u000estruct DaemonStatusJson {█...█#[serde(default, skip_serializing_if = \"Option::is_none\")]█project_index: Option<spt_daemon::projwriter::IndexWriterStats>,█/// MESH-RECOVERY (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH): the pump's█/// reachability snapshot - live peer count, last successful dial, last█/// admitted registry update, all-peer-failure window, per-peer failing█/// stages. ADDITIVE (an N-1 reader ignores it); `None` = no snapshot on█/// this home yet.█#[serde(default, skip_serializing_if = \"Option::is_none\")]█peer_health: Option<spt_daemon::pump::health::PumpHealth>,█/// The rendered verdict over `peer_health`: `idle` / `healthy` /█/// `connecting` / `degraded`. Omitted with it.█#[serde(default, skip_serializing_if = \"Option::is_none\")]█peer_health_verdict: Option<String>,█}█/// One member subnet's count row in the daemon status view.█// [impl->REQ-CLI-JSON]█#[derive(serde::Serialize)]█struct DaemonSubnetJson {█name: String,█nodes: usize,█endpoints: usize,█}█/// F-025 running-image surface: compose the `daemon status` line reporting the█/// version the BROKER process is actually executing beside the installed C [...1864ch elided...] ility health line (ADR-0039 Decision 5, REQ-PUMP-STAGE-TRUTH):█/// the answer to \"can I reach anyone?\" - the question the 2026-07-10 sequester█/// proved `net_up`/heartbeat/counts all dodge. Degraded NAMES the stage the█/// dials die in; healthy requires a live peer connection (real progress), so█/// the incident fingerprint (all dials failing, everything else green) renders█/// DEGRADED here. `None` = no health snapshot (older daemon / no pump yet) or█/// a solo node with nobody to reach - silent, not alarming. Pure for tests.█// [impl->REQ-PUMP-STAGE-TRUTH]█fn render_peer_health_line(█health: Option<&spt_daemon::pump::health::PumpHealth>,█now: u64,█) -> Option<String> {█use spt_daemon::pump::health::HealthVerdict;█...█}█[Showing lines 4179-4248 of 19062. Use :4249 to continue]\u000f█</out>█# Tool call ¶█//Reading redispatch hazard█read(path=\"docs/KNOWN-HAZARDS.md\", selector=\"735-770\")█<out>█\u000e# Known Hazards█...█## Conformance checklist (condensed)█...█| # | Invariant | spt-core surface |█...█| 7.20 | `spt rc` must forward the SCROLL wheel to the harness - our mouse capture (for right-click paste, 7.18) steals WT's native scroll. Track the harness's mouse-reporting mode from its OUTPUT (DECSET `ESC[?1000/1002/1003h` + `1006h` SGR, and `...l` off; scan survives a split across output chunks) into `MouseMode{enabled,sgr}`; forward `ScrollUp/Down` as an xterm SGR report (`ESC[<64/65;col+1;row+1M`) ONLY when `enabled && sgr`, else drop; Moved/drag/clicks dropped. cfg(windows) only - Unix scrolls natively | `rc.rs` `MouseMode`/`MouseModeScanner`/`scroll_dir`/`scroll_sgr`/`spawn_stdin_reader_events`, unit-only (live mouse = HITL) |█| 7.21 | Exactly ONE LIVE `controller_writer` per brain<->broker connection + every writer emits an ASCENDING seq stream => a snap-above consumer over the surviving writer's COMPLETE `[0,end]` replay delivers `[K,end]` with no skip/dup. A brain-restart re-serve double-registered the controller (handoff's `subscribe(from_seq=K)` + the re-serve's `attach_as(sid,0)`) -> two writers raced one socket -> the consumer's strict reject-gap legacy path saw `got seq 1 want [...7492ch elided...] | broker `conn.rs` `BrokerConn`, all write sites (`controller_writer`/`viewer_writer`/`send_frame`/nethost), int `brain_decouple.rs` |█| 7.31 | The Psyche failure budget counts REAL per-event attempts - every bounded per-event turn feeds a consecutive-N budget (default 3), so per-event churn a resident rate-guard was blind to now counts by construction. Fixes hall-bf's ~12/min re-host churn that ran invisibly (boot records weren't ledger boundaries to the old guard) | `lifecycle.rs` `note_turn_outcome`/`psyche_turn_strikes_exhausted` (stamps `psyche_host_error` on exhaustion, no `status` de-stamp), unit synthetic failure-loop trips the budget (RED-first control) |█<!-- [doc->REQ-HAZARD-THRASH-GUARD-BLIND] -->█<!-- [doc->REQ-HAZARD-STORE-INIT-RACE] -->█[Some lines truncated to 768 chars]\u000f█</out>█# Tool call ¶█//Finding v34 release validation█grep(pattern=\"counter 61|v0\\\\.34\\\\.0|REDISPATCH-TRUTH W1|W1 gate|GATE.*redispatch|redispatch.*PASS\", path=\"docs; .git/logs; CHANGELOG.md\", selector=null, case=false, gitignore=false, skip=null)█<out>█\u000e# docs/█## KNOWN-HAZARDS.md█667|- **Failure (paid-for, hertz field RCA 2026-07-16 - 4/5 endpoints frozen, twice in one day):** every brain cycle (`spt daemon refresh`, `spt update` apply - same `applyhost.rs` path) launches a fresh `dispatch::run_dispatch_loop` whose EMPTY per-process `claimed` set enumerates every broker-held peer stream. `NetShared.streams` has NO removal path (`StreamLog::finish` only marks) and the claim condition has no finished filter -> the fresh dispatcher replays FINISHED historical attach streams. A repl...█*668|- **Invariant:** redispatch eligibility is LIFECYCLE-gated: a finished/terminal stream row is retired from the dispatcher's enumeration (a fresh dispatcher never re-serves a terminal Attach); classification identity is restart-durable and independent of the evictable data ring (immutable opener fact pinned per stream until close); claims are retryable on transient worker-setup failure and terminal on terminal outcomes (no hot-loop). The legitimate same-`by` successor re-take after a brain restart still s...█669|- **spt-core mapping:** `spt-daemon/src/dispatch.rs` `run_dispatch_loop` claim condition + `peek_first_line` classification + worker spaw [...9044ch elided...] 43d83d9 898401c1677c3749ca810b9e67e8e9b7252f6e7c Reavo End <decidel@gmail.com> 1782395914 -0700 commit: docs(v0.16.0): ratify W2 - self_verifies attestation-only + sentinel token█8|898401c1677c3749ca810b9e67e8e9b7252f6e7c a0f89796bbdce1aec071c8d6fb26894c6fa64ce5 Reavo End <decidel@gmail.com> 1782396886 -0700 commit: feat(cli): W3 - remove --reply-to + empty-scope create (v0.16.0)█### release/█#### v0.34.0█1|0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700 branch: Created from 01d2d18█*2|01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700 commit: release: v0.34.0 (counter 61) - REDISPATCH-TRUTH W1█[Some lines truncated to 512 chars]\u000f█</out>"}}}
{"type":"message","id":"4b343c3f","parentId":"5dab698f","timestamp":"2026-07-17T00:18:36.347Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_4zOpCqqMHWSUv0oRmbwFUza5|fc_0ceecce325b7fe2d016a5974daeec081988634a67a3d070829","name":"grep","arguments":{"i":"Reading dispatcher cadence constant","pattern":"DEFAULT_DISPATCH_POLL","path":"crates/spt-daemon/src/dispatch.rs","selector":"1-920","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":40062,"output":61,"cacheRead":0,"cacheWrite":0,"totalTokens":40123,"cost":{"input":0.20031000000000002,"output":0.00183,"cacheRead":0,"cacheWrite":0,"total":0.20214000000000001}},"stopReason":"toolUse","timestamp":1784247512088,"responseId":"resp_0ceecce325b7fe2d016a5974d89cc08198a985e38e7ffa27d7","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_0ceecce325b7fe2d016a5974daeec081988634a67a3d070829","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading dispatcher cadence constant\",\"pattern\":\"DEFAULT_DISPATCH_POLL\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"1-920\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_4zOpCqqMHWSUv0oRmbwFUza5","name":"grep"}]},"duration":4257.991500005126,"ttft":2836.3128999993205,"contextSnapshot":{"promptTokens":40062,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_4zOpCqqMHWSUv0oRmbwFUza5|fc_0ceecce325b7fe2d016a5974daeec081988634a67a3d070829","toolName":"grep","startedAt":"2026-07-17T00:18:36.347Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading dispatcher cadence constant"},"id":"198320a8","parentId":"4b343c3f","timestamp":"2026-07-17T00:18:36.347Z"}
{"type":"message","id":"189ab7ee","parentId":"198320a8","timestamp":"2026-07-17T00:18:36.351Z","message":{"role":"toolResult","toolCallId":"call_4zOpCqqMHWSUv0oRmbwFUza5|fc_0ceecce325b7fe2d016a5974daeec081988634a67a3d070829","toolName":"grep","content":[{"type":"text","text":" 86|/// workers block on their stream's event feed).\n*87|pub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n 88|\n 89|/// What protocol a peer-initiated stream speaks, by its first record's shape.\n 90|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n...\n 894|        if let Err(e) =\n*895|            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 896|        {\n 897|            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 898|        }"}],"details":{"scopePath":"crates/spt-daemon/src/dispatch.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/dispatch.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":2,"fileCount":1,"files":["crates/spt-daemon/src/dispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":2}],"truncated":false,"displayContent":"  86│/// workers block on their stream's event feed).\n *87│pub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n  88│\n  89│/// What protocol a peer-initiated stream speaks, by its first record's shape.\n  90│#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n    │...\n 894│        if let Err(e) =\n*895│            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)\n 896│        {\n 897│            eprintln!(\"DISPATCH_LOOP_FAIL: {e}\");\n 898│        }"},"isError":false,"timestamp":1784247516351}}
{"type":"message","id":"a55cc79f","parentId":"189ab7ee","timestamp":"2026-07-17T00:18:41.085Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Distinguishing tag objects from commit targets**"},{"type":"toolCall","id":"call_Gu3Kp7gtvWL9WYTc8wntD301|fc_0ceecce325b7fe2d016a5974e0713881989c2bd9aa97751dbb","name":"read","arguments":{"i":"Capturing release contract text","path":"CHANGELOG.md","selector":"43-60"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":2893,"output":159,"cacheRead":37376,"cacheWrite":0,"totalTokens":40428,"reasoningTokens":124,"cost":{"input":0.014465,"output":0.00477,"cacheRead":0.018688,"cacheWrite":0,"total":0.037923}},"stopReason":"toolUse","timestamp":1784247516359,"responseId":"resp_0ceecce325b7fe2d016a5974dcc2d8819895595447d6f09edc","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5974ddb7788198ac452a2269d51b50","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXTggCKxG2QkxjHjwwx_wjQsLLEV0lXQ3oIZersdwxoagBKESsfOdndCK0EAuZiQWxbJxaD7vqnOm_FrwYEtNIOBhrtBvRh-tIpa6srqxSqTyaEaxKBsQD7-feUnKNzokfBeAnVRORoxgtAG9ePU4yViSFBjtsQQQIF0M6f2v9pPykIo1LWG9tuDfadX8Nvgx6dr875ToOoBoUC7zLtXFMQQlZqywwoTCYzd0YVszfPABBLnyiPVJ2mwJRs0ZDpR8x746Ytd9V6z3RZh7z40pMlj2pk3aFKU7PmK9xM08QRbAZvXvtoxuiwwrqNaGKOrpQHXB990JfTXUGBL-W0m1mIGTJPqx0eOFOL1FEBca5eFVSqjkXgcVk609LXmGU0fn3WDHM9QHOPdfe4QS25VEPikvIGUFHNmpwx-liCpoKnJKNSIz_t2VbxFT1xXI0bHPsElV6r6oRJc25r8-Q_uDbFDsFl0KCxJJuHdLyuxSeuA_yLxQ0zwpDlS2znGj2pe3mMQHKQ8Rh19Ud3Nk1VMFnNx-8bllf2d-dHvzc0X_xtFKULbKty-1zlZEJj11wP7s-QFfbEBrSI9uoSy1Isut_TvY_PWSVf5YAbzmj58ePhrMIcFAYM1N9xDUzR2HZ330H-A2H4fFhjdIieeOGKuKfPI-gjhPVKjYHMeRP2hFj6kE2df_LgCL8OoiZhBvdPbsgYI2LVZWehWIe3W3kXaTeTAZtXznjFk-qcxBAqcMA611tioEdZkkjwicYJDTxh0vY6fkEQdKNFUtfJUtWpn5t4OhEp7zeYL9MJkd6NUR8kXDNLCXt7lu5FBXqKbsHi-vPuA40UAoC6bJoBpvmDsJpmQx-Gbgj3lGAa80SxGy7fhzOp4L5rE3KOAVx7WKonuVJavcXOooyiMe7wWziLkbqGYEa3-qiCjr_a8-zO3M7bnxRaItD3dusrUGrWoxBG6rDpR8CCHpt_sBKjWtaFx4nVkhmGTsiB9qiMLwultodxJeTxxedSX3CromHT_L2avBuuPylSn8IuvxQhdzDIiVA65N6-ceo6lrLPW2ArSb0skMFokTWXfVufriOYdhv_W00LmVoMPZi6vJ40sSODx9N2JMXf_hozJex-EgwPlnr-VCdo1IeppHkgnWgJUD8TeYvMlhCgEj5pvOIbvE2_xjZ-Ne1OahHOq0CAXVFx6WEU4koJoHfNNKxbQIJgc-usq9cPQb1-N-CGqrZPEtZ2CBpVGXPAeCBhL861zDccjP_LiJOqOJEiJltK3UUuQPvTIe4pUwKxl4-6q_TSNHae7ON2KrmUPgpAvimn6XlrCYwg2bee2DWVnSwQwbGRE65_WcWIE8KmMUXFtGo8LGzK_uBLZqa2bfHYXjjkeU0FQjawAqf7ltx709OPV-U5_wTY8rz0AopD1adoAsj9OIxqWyO8GXZFYoTvtMHOcHNwv63pJYjD5asP1gxeEu0tJjj8Ka_ChhnzfKtNCWjCgOfFuIFxWFMsJIrTqDWBksTrio4Dx7irI73k3XBHfxNYuJKZ278SfdDpYEpfS-U_THheCu61-XxuSes95N1cAKiONHCx-5C8=","summary":[{"type":"summary_text","text":"**Distinguishing tag objects from commit targets**"}]},{"id":"fc_0ceecce325b7fe2d016a5974e0713881989c2bd9aa97751dbb","type":"function_call","status":"completed","arguments":"{\"i\":\"Capturing release contract text\",\"path\":\"CHANGELOG.md\",\"selector\":\"43-60\"}","call_id":"call_Gu3Kp7gtvWL9WYTc8wntD301","name":"read"}]},"duration":4725.127300001681,"ttft":1351.1284999996424,"contextSnapshot":{"promptTokens":40269,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Gu3Kp7gtvWL9WYTc8wntD301|fc_0ceecce325b7fe2d016a5974e0713881989c2bd9aa97751dbb","toolName":"read","startedAt":"2026-07-17T00:18:41.086Z","args":{"path":"CHANGELOG.md"},"intent":"Capturing release contract text"},"id":"0b638733","parentId":"a55cc79f","timestamp":"2026-07-17T00:18:41.086Z"}
{"type":"message","id":"48132a1d","parentId":"0b638733","timestamp":"2026-07-17T00:18:41.110Z","message":{"role":"toolResult","toolCallId":"call_Gu3Kp7gtvWL9WYTc8wntD301|fc_0ceecce325b7fe2d016a5974e0713881989c2bd9aa97751dbb","toolName":"read","content":[{"type":"text","text":"## [0.35.0] - 2026-07-16\n…\n### Fixed\n…\n- **A node no longer strands itself from its subnets after a brief failure to\n…\n- **Poisoned peer-address entries are repaired at startup.** If a cached peer\n…\n\n## [0.34.0] - 2026-07-16\n\nA stability fix for live remote sessions during a daemon update.\n\n### Fixed\n\n- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\n  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\n  A daemon cycle could previously replay already-finished output onto a live\n  remote terminal: old text re-typed itself, control of the session was stolen,\n  or a running session was left frozen. The daemon now treats a finished\n  session's history as terminal and never re-drives it onto a live terminal, so\n  the remote sessions you are attached to keep running cleanly across an update.\n\n## [0.33.0] - 2026-07-16\n\nA performance and identity release. The commands that list endpoints and choose\na run now answer in a fraction of a second instead of many seconds, `spt whoami`\nbecomes a focused identity command, and the daemon gains a health report for the\nindex that makes the fast listings possible.\n\n…\n\n\n[Showing lines 42-63 of 1126. Use :64 to continue]"}],"details":{"truncation":{"content":"\r\n## [0.34.0] - 2026-07-16\r\n\r\nA stability fix for live remote sessions during a daemon update.\r\n\r\n### Fixed\r\n\r\n- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\r\n  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\r\n  A daemon cycle could previously replay already-finished output onto a live\r\n  remote terminal: old text re-typed itself, control of the session was stolen,\r\n  or a running session was left frozen. The daemon now treats a finished\r\n  session's history as terminal and never re-drives it onto a live terminal, so\r\n  the remote sessions you are attached to keep running cleanly across an update.\r\n\r\n## [0.33.0] - 2026-07-16\r\n\r\nA performance and identity release. The commands that list endpoints and choose\r\na run now answer in a fraction of a second instead of many seconds, `spt whoami`\r\nbecomes a focused identity command, and the daemon gains a health report for the\r\nindex that makes the fast listings possible.\r\n\r","truncated":true,"truncatedBy":"lines","totalLines":1085,"totalBytes":996,"outputLines":22,"outputBytes":996,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"## [0.35.0] - 2026-07-16\n…\n### Fixed\n…\n- **A node no longer strands itself from its subnets after a brief failure to\n…\n- **Poisoned peer-address entries are repaired at startup.** If a cached peer\n…\n\r\n## [0.34.0] - 2026-07-16\r\n\r\nA stability fix for live remote sessions during a daemon update.\r\n\r\n### Fixed\r\n\r\n- Updating or refreshing the daemon — `spt update`, `spt update --restart`, or\r\n  `spt daemon refresh` — no longer disturbs a remote session you are attached to.\r\n  A daemon cycle could previously replay already-finished output onto a live\r\n  remote terminal: old text re-typed itself, control of the session was stolen,\r\n  or a running session was left frozen. The daemon now treats a finished\r\n  session's history as terminal and never re-drives it onto a live terminal, so\r\n  the remote sessions you are attached to keep running cleanly across an update.\r\n\r\n## [0.33.0] - 2026-07-16\r\n\r\nA performance and identity release. The commands that list endpoints and choose\r\na run now answer in a fraction of a second instead of many seconds, `spt whoami`\r\nbecomes a focused identity command, and the daemon gains a health report for the\r\nindex that makes the fast listings possible.\r\n\r\n…\n","startLine":12,"lineNumbers":[12,null,28,null,30,null,38,null,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,null,106]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\CHANGELOG.md"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1126,"totalBytes":996,"outputLines":22,"outputBytes":996,"shownRange":{"start":42,"end":63},"nextOffset":64}}},"isError":false,"timestamp":1784247521110}}
{"type":"message","id":"1061cab9","parentId":"48132a1d","timestamp":"2026-07-17T00:18:41.111Z","message":{"role":"user","content":"Your current interruptible wait was interrupted because an IRC message arrived from your parent agent `Main`.\n\nParent IRC message:\n\nField trace: current v0.34 cold generation has 33 CONN_WRITE_POISONED stream-subscriber incidents. Every write poisons at ~15000ms; corresponding DISPATCH failure lands 15.1–62s after write start (recent 17.6,25.4,29.1,30.1,31.1,36.6,59.4,61.6s). Median poison interarrival 95.5s, min 29.7s. 4,361 stream-sub-attach records and 584 PUMP_PEER_FAIL 10s events. Recent webbie session controller attach at wall 1784245849362; stream 4834 write started +37.354s, poisoned +52.366s. Please incorporate/falsify causal tie to PTY RC stall and identify why DISPATCH completion remains delayed after poison.","attribution":"agent","timestamp":1784247520231,"steering":true}}
{"type":"message","id":"59c09324","parentId":"1061cab9","timestamp":"2026-07-17T00:18:49.598Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Tracing connection writer and dispatcher failure**"},{"type":"toolCall","id":"call_bf3Z2WI4iIhPSnNbIzHHyrv5|fc_0ceecce325b7fe2d016a5974e927a08198b59f480c6af973f3","name":"grep","arguments":{"i":"Tracing poisoned writer path","pattern":"CONN_WRITE_POISONED|WRITE_POISONED|BRAIN_WRITE_DEADLINE|CONTROLLER_WRITE_DEADLINE|write_poison","path":"crates","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_fL9fp7t98TJdAUueomuB5npk|fc_0ceecce325b7fe2d016a5974e927b8819899d100a322ad56aa","name":"grep","arguments":{"i":"Tracing stream subscriber worker","pattern":"stream-subscriber|stream subscriber|net-stream-sub|net_stream_subscribe|KIND_NET_STREAM_SUB","path":"crates/spt-daemon/src","selector":null,"case":false,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_EPXfxv9fRbKlVflazRkPoQK1|fc_0ceecce325b7fe2d016a5974e927cc819899e3ca948aaa09c2","name":"grep","arguments":{"i":"Finding dispatch completion waits","pattern":"join\\(|JoinHandle|thread::spawn|retire_stream|ClaimState::Failed|attempts","path":"crates/spt-daemon/src/dispatch.rs","selector":"150-900","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3642,"output":296,"cacheRead":37376,"cacheWrite":0,"totalTokens":41314,"reasoningTokens":62,"cost":{"input":0.01821,"output":0.00888,"cacheRead":0.018688,"cacheWrite":0,"total":0.045778}},"stopReason":"toolUse","timestamp":1784247521132,"responseId":"resp_0ceecce325b7fe2d016a5974e173888198b8d40de78721a0e7","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5974e3b5b88198ac296a865a21043a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXTp0j8Q-tJJczIbpmS-mZPQ1GYM-ZMoBOIuyaR5u_o8kz3e4aLBQe-TSj4V_ik8Fv_smOIBk3mLyduyUcR-RVh6NG_dS_JMf8LUQwFTUrdqVxx2qlCvopvt4rLf88nGqAfJFYUyNbhBBJ9bL_pKoxZL2ZHx6CnigGZMJ-d1P-Rz-C0iFH0sV_hWQ3OLkxOQ6Ta6gBGMP3CUbg58V2jrACjdWqQ9-vj-iKfwlKQCbvlK9Ws5jwljLu2lCX2DeQweitTtg4EbucaYQxk7CgRapsAc_p3GxOQNyyMyjWF4t4BtCiZ-4mTC1F1AIQ9SCj-HAwrWZfxAA4oeq_8qL9G8u2GY-6JAor0RztQebMjRylWzEZCC3VYgChgO4thYfnZIMB4Hl7Eny3SGU9lBLHiSxVob1i0AFyI9Jj-Dr1x_jn_m6OP6S5437hXm2Zy9m9uL66rjciKG6MhioTrbU893qaNYtwI6fkEJvS7nCa66j7J4SlOBDDDEzsLIsvK3SHGteAOyIjoOmdqK3kgKoie22Sx08bTZbTAUK1hRk9qbZcQ9IG7HIkdlFeNzzUAjFOVDZ-y9Puyj4pb5fm4cJnSgywNoNXULqy6E35hIiWtYauT9A1vek5pXXNA23mgrqn3KXcr6iT83QTH29ec3zYwRgiGslvxbFEbOh9AFaWuyJ-zEZP59eiEn5ad_22fwuETBrsscu65H3u0qc3bptseWV6b58MoQ4nibBZnDVT4IlhmKPgATJqqyyXGYgzcc6Hoxj--YpnuyCWRIbwCOplGn44kRC57FXcB_uqrnk2JRNLLiBYss0XRHN00hxtlg7Hj6NT7bMMWAnSU4gALgWVr_8y-RfNFKCasW1LLe1sgPrveFtkqMPp_OA6RM70SFeiUsQSl2N2kyE2jxHtGxGPdbY2MtSOj8uO8uSqQvi66mEoIcsKTZ1HRxYnlmSkA8daSZuoStp59c6gTNJ_S1WB_r0QeFyuCGWtXhdzswd-BWmleUR7DINNGl7gg9x2-pCxI_xeun_uNq3Rejaec1jrz15Or01eAz5pwwoiHlClj7Zd-xwvbVfFRpMWpXUDKlQIJ8wTR-tSmIDEwIHD9u0pI8_4oY7nNic5kXNTvoUs8JoSN3JMnqc4EAEf7u8YZJ3ylH8ui497Jl_YdHUumSijvy3TVvsapvBKexCHNbeUg0v7ZVomffn9hO-OjpHqR0lpcfiYs_MoVjBndftA8aBvhZhNfP1orJtxqZB8QN4wiRUzbUT4rH_HzyYF9ko2g0O-se9c8t2iEajUIRSMd9Iho9TI4gTf9Zm7gSsyy_xrr566LMHf4E_J97oOS5fE1i5h4nCZVv","summary":[{"type":"summary_text","text":"**Tracing connection writer and dispatcher failure**"}]},{"id":"fc_0ceecce325b7fe2d016a5974e927a08198b59f480c6af973f3","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing poisoned writer path\",\"pattern\":\"CONN_WRITE_POISONED|WRITE_POISONED|BRAIN_WRITE_DEADLINE|CONTROLLER_WRITE_DEADLINE|write_poison\",\"path\":\"crates\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_bf3Z2WI4iIhPSnNbIzHHyrv5","name":"grep"},{"id":"fc_0ceecce325b7fe2d016a5974e927b8819899d100a322ad56aa","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing stream subscriber worker\",\"pattern\":\"stream-subscriber|stream subscriber|net-stream-sub|net_stream_subscribe|KIND_NET_STREAM_SUB\",\"path\":\"crates/spt-daemon/src\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_fL9fp7t98TJdAUueomuB5npk","name":"grep"},{"id":"fc_0ceecce325b7fe2d016a5974e927cc819899e3ca948aaa09c2","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding dispatch completion waits\",\"pattern\":\"join\\\\(|JoinHandle|thread::spawn|retire_stream|ClaimState::Failed|attempts\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"150-900\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_EPXfxv9fRbKlVflazRkPoQK1","name":"grep"}]},"duration":8452.891800001264,"ttft":2568.2292000055313,"contextSnapshot":{"promptTokens":41018,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_bf3Z2WI4iIhPSnNbIzHHyrv5|fc_0ceecce325b7fe2d016a5974e927a08198b59f480c6af973f3","toolName":"grep","startedAt":"2026-07-17T00:18:49.598Z","args":{"path":"crates"},"intent":"Tracing poisoned writer path"},"id":"701229c3","parentId":"59c09324","timestamp":"2026-07-17T00:18:49.598Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_fL9fp7t98TJdAUueomuB5npk|fc_0ceecce325b7fe2d016a5974e927b8819899d100a322ad56aa","toolName":"grep","startedAt":"2026-07-17T00:18:49.598Z","args":{"path":"crates/spt-daemon/src"},"intent":"Tracing stream subscriber worker"},"id":"a576bda6","parentId":"701229c3","timestamp":"2026-07-17T00:18:49.598Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_EPXfxv9fRbKlVflazRkPoQK1|fc_0ceecce325b7fe2d016a5974e927cc819899e3ca948aaa09c2","toolName":"grep","startedAt":"2026-07-17T00:18:49.598Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Finding dispatch completion waits"},"id":"8768df52","parentId":"a576bda6","timestamp":"2026-07-17T00:18:49.598Z"}
{"type":"message","id":"95c8c162","parentId":"8768df52","timestamp":"2026-07-17T00:18:49.600Z","message":{"role":"toolResult","toolCallId":"call_EPXfxv9fRbKlVflazRkPoQK1|fc_0ceecce325b7fe2d016a5974e927cc819899e3ca948aaa09c2","toolName":"grep","content":[{"type":"text","text":" 176|enum ClaimState {\n*177|    /// A worker owns the stream right now (`attempts` = prior failures).\n*178|    InFlight { attempts: u32 },\n 179|    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n 180|    /// retries exhausted. Terminal outcomes never re-enter the queue.\n 181|    Terminal,\n 182|    /// A transient failure released the claim; eligible again at `next_at`.\n*183|    Retry { attempts: u32, next_at: Instant },\n 184|}\n 185|\n 186|/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n...\n 192|\n*193|/// The backoff before retry number `attempts` (1-based) runs.\n*194|fn retry_backoff(attempts: u32) -> Duration {\n*195|    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n 196|}\n 197|\n 198|/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n...\n 204|        None => Some(0),\n*205|        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n 206|        _ => None,\n 207|    }\n 208|}\n...\n 214|// [impl->REQ-DISPATCH-CLAIM-RETRY]\n*215|fn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\n 216|    match outcome {\n 217|        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n 218|        DispatchOutcome::Failed(_) => {\n*219|            let attempts = attempts + 1;\n*220|            if attempts >= MAX_DISPATCH_ATTEMPTS {\n 221|                ClaimState::Terminal\n 222|            } else {\n 223|                ClaimState::Retry {\n*224|                    attempts,\n*225|                    next_at: now + retry_backoff(attempts),\n 226|                }\n 227|            }\n 228|        }\n...\n 288|            owlery: perch::owlery_dir(),\n*289|            releases: home.join(\"releases\"),\n*290|            scratch: home.join(\"scratch\").join(\"net\"),\n 291|        }\n 292|    }\n 293|}\n...\n 322|            }\n*323|            let attempts = {\n 324|                let mut c = claims.lock().unwrap();\n 325|                match should_claim(c.get(&s.stream_id), Instant::now()) {\n*326|                    Some(attempts) => {\n*327|                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n*328|                        attempts\n 329|                    }\n 330|                    None => continue,\n 331|                }\n...\n 340|            let claims_done = Arc::clone(&claims);\n*341|            thread::spawn(move || {\n 342|                let outcome = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n 343|                if matches!(outcome, DispatchOutcome::Served(_)) {\n 344|                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n...\n 358|                    stream_id,\n*359|                    outcome_transition(&outcome, attempts, Instant::now()),\n 360|                );\n 361|            });\n 362|        }\n...\n 608|                    let root = spt_store::xfer::transfers_dir();\n*609|                    let progress = paths.scratch.join(\"xfer-progress\");\n 610|                    match serve_xfer(\n 611|                        &mut brain, stream_id, &endpoint, origin, 0, &root, &progress,\n 612|                    ) {\n...\n 719|                    let local = perch::resolve_perch_path(&id, spt_store::perch::ParentHint::Infer);\n*720|                    if local.join(\"info.json\").exists() {\n 721|                        if let Err(e) = crate::resting::daemon_rest_event(\n 722|                            &id,\n 723|                            crate::resting::RestEvent::AttentionShift,\n...\n 890|    stop: Arc<AtomicBool>,\n*891|) -> thread::JoinHandle<()> {\n*892|    thread::spawn(move || {\n 893|        let paths = DispatchPaths::production();\n 894|        if let Err(e) =\n 895|            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)"}],"details":{"scopePath":"crates/spt-daemon/src/dispatch.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/dispatch.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":24,"fileCount":1,"files":["crates/spt-daemon/src/dispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":24}],"truncated":false,"displayContent":" 176│enum ClaimState {\n*177│    /// A worker owns the stream right now (`attempts` = prior failures).\n*178│    InFlight { attempts: u32 },\n 179│    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n 180│    /// retries exhausted. Terminal outcomes never re-enter the queue.\n 181│    Terminal,\n 182│    /// A transient failure released the claim; eligible again at `next_at`.\n*183│    Retry { attempts: u32, next_at: Instant },\n 184│}\n 185│\n 186│/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n    │...\n 192│\n*193│/// The backoff before retry number `attempts` (1-based) runs.\n*194│fn retry_backoff(attempts: u32) -> Duration {\n*195│    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n 196│}\n 197│\n 198│/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n    │...\n 204│        None => Some(0),\n*205│        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n 206│        _ => None,\n 207│    }\n 208│}\n    │...\n 214│// [impl->REQ-DISPATCH-CLAIM-RETRY]\n*215│fn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\n 216│    match outcome {\n 217│        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n 218│        DispatchOutcome::Failed(_) => {\n*219│            let attempts = attempts + 1;\n*220│            if attempts >= MAX_DISPATCH_ATTEMPTS {\n 221│                ClaimState::Terminal\n 222│            } else {\n 223│                ClaimState::Retry {\n*224│                    attempts,\n*225│                    next_at: now + retry_backoff(attempts),\n 226│                }\n 227│            }\n 228│        }\n    │...\n 288│            owlery: perch::owlery_dir(),\n*289│            releases: home.join(\"releases\"),\n*290│            scratch: home.join(\"scratch\").join(\"net\"),\n 291│        }\n 292│    }\n 293│}\n    │...\n 322│            }\n*323│            let attempts = {\n 324│                let mut c = claims.lock().unwrap();\n 325│                match should_claim(c.get(&s.stream_id), Instant::now()) {\n*326│                    Some(attempts) => {\n*327│                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n*328│                        attempts\n 329│                    }\n 330│                    None => continue,\n 331│                }\n    │...\n 340│            let claims_done = Arc::clone(&claims);\n*341│            thread::spawn(move || {\n 342│                let outcome = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n 343│                if matches!(outcome, DispatchOutcome::Served(_)) {\n 344│                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n    │...\n 358│                    stream_id,\n*359│                    outcome_transition(&outcome, attempts, Instant::now()),\n 360│                );\n 361│            });\n 362│        }\n    │...\n 608│                    let root = spt_store::xfer::transfers_dir();\n*609│                    let progress = paths.scratch.join(\"xfer-progress\");\n 610│                    match serve_xfer(\n 611│                        &mut brain, stream_id, &endpoint, origin, 0, &root, &progress,\n 612│                    ) {\n    │...\n 719│                    let local = perch::resolve_perch_path(&id, spt_store::perch::ParentHint::Infer);\n*720│                    if local.join(\"info.json\").exists() {\n 721│                        if let Err(e) = crate::resting::daemon_rest_event(\n 722│                            &id,\n 723│                            crate::resting::RestEvent::AttentionShift,\n    │...\n 890│    stop: Arc<AtomicBool>,\n*891│) -> thread::JoinHandle<()> {\n*892│    thread::spawn(move || {\n 893│        let paths = DispatchPaths::production();\n 894│        if let Err(e) =\n 895│            run_dispatch_loop(&broker_name, &paths, registry, &stop, DEFAULT_DISPATCH_POLL)"},"isError":false,"timestamp":1784247529600}}
{"type":"message","id":"6a6a5487","parentId":"95c8c162","timestamp":"2026-07-17T00:18:49.619Z","message":{"role":"toolResult","toolCallId":"call_fL9fp7t98TJdAUueomuB5npk|fc_0ceecce325b7fe2d016a5974e927b8819899d100a322ad56aa","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## attach.rs\n 302|) -> io::Result<AttachServeOutcome> {\n*303|    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 304|    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n 305|    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n 306|    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n## brain.rs\n 53|    KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED,\n*54|    KIND_MET_MEMBER, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET,\n 55|    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,\n 56|    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,\n 57|};\n...\n 271|    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\n*272|    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n 273|    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n 274|    /// Durability across a handoff is the logic layer's job (a re-attaching\n 275|    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n...\n 883|    /// broker-IPC-CARRIER desync (the carrier ops — `net_open_stream` /\n*884|    /// `net_stream_subscribe` / `net_stream_send` — keep their raw `TimedOut`).\n 885|    /// Abandoning the peer's stream is safe: the exactly-once seq cursor stays\n 886|    /// contiguous, and a late reply on the dropped stream id matches nothing (the\n 887|    /// leg loops treat it as noise) — the desync the old blanket-poison feared\n...\n 1588|    // [impl->REQ-DAEMON-2]\n*1589|    pub fn net_stream_subscribe(&mut self, stream_id: u64, from_seq: u64) -> io::Result<()> {\n 1590|        self.net_cursors.insert(stream_id, from_seq);\n 1591|        self.send(\n*1592|            KIND_NET_STREAM_SUBSCRIBE,\n 1593|            serde_json::to_value(NetStreamSubscribeReq {\n 1594|                stream_id,\n 1595|                from_seq,\n## broker.rs\n 66|    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n*67|    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n 68|    KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n 69|    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n 70|};\n...\n 2998|        // Base attribution fact (F-039 leg b): broker clients are brains; role\n*2999|        // facts (controller/viewer/presence/stream subscriber) accrete at the\n 3000|        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n 3001|        send.describe(\"role=brain\");\n 3002|\n...\n 3190|                }\n*3191|                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n 3192|                    Ok(id) => my_stream_subs.push(id),\n 3193|                    Err(msg) => send_error(&send, &msg),\n 3194|                },\n...\n 4361|    // [impl->REQ-DAEMON-2]\n*4362|    fn dispatch_net_stream_subscribe(\n 4363|        &self,\n 4364|        env: Envelope,\n 4365|        send: &SharedSend,\n...\n 4367|        let req: NetStreamSubscribeReq = serde_json::from_value(env.payload)\n*4368|            .map_err(|e| format!(\"bad net-stream-subscribe payload: {e}\"))?;\n 4369|        let host = self.net.get().ok_or(\"net disabled\")?;\n 4370|        host.subscribe_stream(req.stream_id, Arc::clone(send), req.from_seq)\n 4371|            .map_err(|e| e.to_string())?;\n## dispatch.rs\n 429|    let mut brain = connect(broker_name)?;\n*430|    brain.net_stream_subscribe(stream_id, floor_seq)?;\n 431|    let mut buf: Vec<u8> = Vec::new();\n 432|    loop {\n 433|        match brain.read_event()? {\n...\n 680|    let policy = RegistryGatePolicy::load();\n*681|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 682|        return DispatchOutcome::Failed(e.to_string());\n 683|    }\n 684|    let mut decoder = RegistryDecoder::new();\n...\n 765|        .unwrap_or_default();\n*766|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 767|        return DispatchOutcome::Failed(e.to_string());\n 768|    }\n 769|    let mut decoder = NotifDecoder::new();\n...\n 838|) -> DispatchOutcome {\n*839|    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 840|        return DispatchOutcome::Failed(e.to_string());\n 841|    }\n 842|    let mut decoder = WanDecoder::new();\n## linkhost.rs\n 453|) -> io::Result<ShellLinkServeOutcome> {\n*454|    brain.net_stream_subscribe(stream_id, 0)?;\n 455|    let mut decoder = ShellLinkDecoder::new();\n 456|    loop {\n 457|        match brain.read_event()? {\n...\n 682|\n*683|    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 684|    let mut decoder = ShellLinkDecoder::new();\n 685|    loop {\n 686|        match brain.read_event()? {\n## msg.rs\n 141|/// the PTY `subscribe` discipline applied to a QUIC stream (gapless resume).\n*142|pub const KIND_NET_STREAM_SUBSCRIBE: &str = \"net-stream-subscribe\";\n 143|/// Broker→brain: a sequenced chunk of a stream's reads (live or replayed).\n 144|pub const KIND_NET_STREAM_DATA: &str = \"net-stream-data\";\n 145|/// Broker→brain: a stream's read side finished cleanly; `next_seq` tells the\n...\n 941|\n*942|/// `net-stream-subscribe` payload — attach this connection as a stream's read\n 943|/// subscriber, replaying buffered chunks with `seq >= from_seq` first (D4b).\n 944|#[derive(Debug, Clone, Serialize, Deserialize)]\n 945|pub struct NetStreamSubscribeReq {\n## nethost.rs\n 322|    fn attach(&mut self, sub: SharedSend, from_seq: u64) {\n*323|        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\n 324|        // record — this is one of the two RCA-pinned shared-carrier classes.\n 325|        // [impl->REQ-CONN-POISON-ATTRIBUTION]\n*326|        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\n 327|        sub.lifecycle_event(\n 328|            \"stream-sub-attach\",\n 329|            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\n## propagate.rs\n 114|) -> io::Result<UpdateServeOutcome> {\n*115|    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 116|    let mut decoder = UpdDecoder::new();\n 117|    // Set once an Offer goes out; the Fetch must echo the same pull id.\n 118|    let mut offered: Option<String> = None;\n...\n 355|    let stream_id = opened.stream_id;\n*356|    brain.net_stream_subscribe(stream_id, 0)?;\n 357|\n 358|    let upd_id = format!(\"upd-{}\", open_op.seq);\n 359|    let query = UpdRecord::Query {\n...\n 557|    let stream_id = opened.stream_id;\n*558|    brain.net_stream_subscribe(stream_id, 0)?;\n 559|\n 560|    let upd_id = format!(\"upds-{stream_id}\");\n 561|    let query = UpdRecord::StatusQuery {\n## resthost.rs\n 81|) -> io::Result<RestServeOutcome> {\n*82|    brain.net_stream_subscribe(stream_id, 0)?;\n 83|    let mut decoder = RestDecoder::new();\n 84|    loop {\n 85|        match brain.read_event()? {\n...\n 201|\n*202|    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 203|    let mut decoder = RestDecoder::new();\n 204|    loop {\n 205|        match brain.read_event()? {\n## serveprobe.rs\n 73|) -> io::Result<ServeProbeServeOutcome> {\n*74|    brain.net_stream_subscribe(stream_id, 0)?;\n 75|    let mut decoder = ServeProbeDecoder::new();\n 76|    loop {\n 77|        match brain.read_event()? {\n...\n 120|\n*121|    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 122|    let mut decoder = ServeProbeDecoder::new();\n 123|    loop {\n 124|        match brain.read_event()? {\n## sync.rs\n 194|) -> io::Result<SyncServeOutcome> {\n*195|    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 196|    let mut decoder = SyncDecoder::new();\n 197|\n 198|    loop {\n...\n 344|    let stream_id = opened.stream_id;\n*345|    brain.net_stream_subscribe(stream_id, 0)?;\n 346|\n 347|    let sync_id = format!(\"pull-{}\", open_op.seq);\n 348|    let mut have_tips = BTreeMap::new();\n## wan.rs\n 202|    brain.net_stream_send(opened.stream_id, &ndjson::encode_line(msg), None, true)?;\n*203|    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 204|    let mut decoder = WanReplyDecoder::new();\n 205|    loop {\n 206|        match brain.read_event()? {\n## xfer.rs\n 233|) -> io::Result<XferServeOutcome> {\n*234|    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 235|    let mut decoder = XferDecoder::new();\n 236|    let mut recv: Option<RecvState> = None;\n 237|\n...\n 436|    let stream_id = opened.stream_id;\n*437|    brain.net_stream_subscribe(stream_id, 0)?;\n 438|    let line = XferRecord::Fetch {\n 439|        path: remote_path.to_string(),\n 440|        xfer_id: xfer_id.to_string(),\n...\n 546|    let stream_id = opened.stream_id;\n*547|    brain.net_stream_subscribe(stream_id, 0)?;\n 548|    let push = XferRecord::Push {\n 549|        path: remote_path.to_string(),\n 550|        total,"}],"details":{"scopePath":"crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":34,"fileCount":13,"files":["crates/spt-daemon/src/attach.rs","crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/linkhost.rs","crates/spt-daemon/src/msg.rs","crates/spt-daemon/src/nethost.rs","crates/spt-daemon/src/propagate.rs","crates/spt-daemon/src/resthost.rs","crates/spt-daemon/src/serveprobe.rs","crates/spt-daemon/src/sync.rs","crates/spt-daemon/src/wan.rs","crates/spt-daemon/src/xfer.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/attach.rs","count":1},{"path":"crates/spt-daemon/src/brain.rs","count":5},{"path":"crates/spt-daemon/src/broker.rs","count":5},{"path":"crates/spt-daemon/src/dispatch.rs","count":4},{"path":"crates/spt-daemon/src/linkhost.rs","count":2},{"path":"crates/spt-daemon/src/msg.rs","count":2},{"path":"crates/spt-daemon/src/nethost.rs","count":2},{"path":"crates/spt-daemon/src/propagate.rs","count":3},{"path":"crates/spt-daemon/src/resthost.rs","count":2},{"path":"crates/spt-daemon/src/serveprobe.rs","count":2},{"path":"crates/spt-daemon/src/sync.rs","count":2},{"path":"crates/spt-daemon/src/wan.rs","count":1},{"path":"crates/spt-daemon/src/xfer.rs","count":3}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## attach.rs\n 302│) -> io::Result<AttachServeOutcome> {\n*303│    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 304│    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n 305│    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n 306│    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n## brain.rs\n   53│    KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED,\n  *54│    KIND_MET_MEMBER, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET,\n   55│    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,\n   56│    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,\n   57│};\n     │...\n  271│    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\n *272│    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n  273│    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n  274│    /// Durability across a handoff is the logic layer's job (a re-attaching\n  275│    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n     │...\n  883│    /// broker-IPC-CARRIER desync (the carrier ops — `net_open_stream` /\n *884│    /// `net_stream_subscribe` / `net_stream_send` — keep their raw `TimedOut`).\n  885│    /// Abandoning the peer's stream is safe: the exactly-once seq cursor stays\n  886│    /// contiguous, and a late reply on the dropped stream id matches nothing (the\n  887│    /// leg loops treat it as noise) — the desync the old blanket-poison feared\n     │...\n 1588│    // [impl->REQ-DAEMON-2]\n*1589│    pub fn net_stream_subscribe(&mut self, stream_id: u64, from_seq: u64) -> io::Result<()> {\n 1590│        self.net_cursors.insert(stream_id, from_seq);\n 1591│        self.send(\n*1592│            KIND_NET_STREAM_SUBSCRIBE,\n 1593│            serde_json::to_value(NetStreamSubscribeReq {\n 1594│                stream_id,\n 1595│                from_seq,\n## broker.rs\n   66│    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,\n  *67│    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_OPENER, KIND_NET_STREAM_OPENER_REPLY, KIND_NET_STREAM_RETIRE, KIND_NET_STREAM_RETIRED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,\n   68│    KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,\n   69│    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,\n   70│};\n     │...\n 2998│        // Base attribution fact (F-039 leg b): broker clients are brains; role\n*2999│        // facts (controller/viewer/presence/stream subscriber) accrete at the\n 3000│        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n 3001│        send.describe(\"role=brain\");\n 3002│\n     │...\n 3190│                }\n*3191│                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n 3192│                    Ok(id) => my_stream_subs.push(id),\n 3193│                    Err(msg) => send_error(&send, &msg),\n 3194│                },\n     │...\n 4361│    // [impl->REQ-DAEMON-2]\n*4362│    fn dispatch_net_stream_subscribe(\n 4363│        &self,\n 4364│        env: Envelope,\n 4365│        send: &SharedSend,\n     │...\n 4367│        let req: NetStreamSubscribeReq = serde_json::from_value(env.payload)\n*4368│            .map_err(|e| format!(\"bad net-stream-subscribe payload: {e}\"))?;\n 4369│        let host = self.net.get().ok_or(\"net disabled\")?;\n 4370│        host.subscribe_stream(req.stream_id, Arc::clone(send), req.from_seq)\n 4371│            .map_err(|e| e.to_string())?;\n## dispatch.rs\n 429│    let mut brain = connect(broker_name)?;\n*430│    brain.net_stream_subscribe(stream_id, floor_seq)?;\n 431│    let mut buf: Vec<u8> = Vec::new();\n 432│    loop {\n 433│        match brain.read_event()? {\n    │...\n 680│    let policy = RegistryGatePolicy::load();\n*681│    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 682│        return DispatchOutcome::Failed(e.to_string());\n 683│    }\n 684│    let mut decoder = RegistryDecoder::new();\n    │...\n 765│        .unwrap_or_default();\n*766│    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 767│        return DispatchOutcome::Failed(e.to_string());\n 768│    }\n 769│    let mut decoder = NotifDecoder::new();\n    │...\n 838│) -> DispatchOutcome {\n*839│    if let Err(e) = brain.net_stream_subscribe(stream_id, 0) {\n 840│        return DispatchOutcome::Failed(e.to_string());\n 841│    }\n 842│    let mut decoder = WanDecoder::new();\n## linkhost.rs\n 453│) -> io::Result<ShellLinkServeOutcome> {\n*454│    brain.net_stream_subscribe(stream_id, 0)?;\n 455│    let mut decoder = ShellLinkDecoder::new();\n 456│    loop {\n 457│        match brain.read_event()? {\n    │...\n 682│\n*683│    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 684│    let mut decoder = ShellLinkDecoder::new();\n 685│    loop {\n 686│        match brain.read_event()? {\n## msg.rs\n 141│/// the PTY `subscribe` discipline applied to a QUIC stream (gapless resume).\n*142│pub const KIND_NET_STREAM_SUBSCRIBE: &str = \"net-stream-subscribe\";\n 143│/// Broker→brain: a sequenced chunk of a stream's reads (live or replayed).\n 144│pub const KIND_NET_STREAM_DATA: &str = \"net-stream-data\";\n 145│/// Broker→brain: a stream's read side finished cleanly; `next_seq` tells the\n    │...\n 941│\n*942│/// `net-stream-subscribe` payload — attach this connection as a stream's read\n 943│/// subscriber, replaying buffered chunks with `seq >= from_seq` first (D4b).\n 944│#[derive(Debug, Clone, Serialize, Deserialize)]\n 945│pub struct NetStreamSubscribeReq {\n## nethost.rs\n 322│    fn attach(&mut self, sub: SharedSend, from_seq: u64) {\n*323│        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\n 324│        // record — this is one of the two RCA-pinned shared-carrier classes.\n 325│        // [impl->REQ-CONN-POISON-ATTRIBUTION]\n*326│        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\n 327│        sub.lifecycle_event(\n 328│            \"stream-sub-attach\",\n 329│            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\n## propagate.rs\n 114│) -> io::Result<UpdateServeOutcome> {\n*115│    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 116│    let mut decoder = UpdDecoder::new();\n 117│    // Set once an Offer goes out; the Fetch must echo the same pull id.\n 118│    let mut offered: Option<String> = None;\n    │...\n 355│    let stream_id = opened.stream_id;\n*356│    brain.net_stream_subscribe(stream_id, 0)?;\n 357│\n 358│    let upd_id = format!(\"upd-{}\", open_op.seq);\n 359│    let query = UpdRecord::Query {\n    │...\n 557│    let stream_id = opened.stream_id;\n*558│    brain.net_stream_subscribe(stream_id, 0)?;\n 559│\n 560│    let upd_id = format!(\"upds-{stream_id}\");\n 561│    let query = UpdRecord::StatusQuery {\n## resthost.rs\n  81│) -> io::Result<RestServeOutcome> {\n *82│    brain.net_stream_subscribe(stream_id, 0)?;\n  83│    let mut decoder = RestDecoder::new();\n  84│    loop {\n  85│        match brain.read_event()? {\n    │...\n 201│\n*202│    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 203│    let mut decoder = RestDecoder::new();\n 204│    loop {\n 205│        match brain.read_event()? {\n## serveprobe.rs\n  73│) -> io::Result<ServeProbeServeOutcome> {\n *74│    brain.net_stream_subscribe(stream_id, 0)?;\n  75│    let mut decoder = ServeProbeDecoder::new();\n  76│    loop {\n  77│        match brain.read_event()? {\n    │...\n 120│\n*121│    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 122│    let mut decoder = ServeProbeDecoder::new();\n 123│    loop {\n 124│        match brain.read_event()? {\n## sync.rs\n 194│) -> io::Result<SyncServeOutcome> {\n*195│    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 196│    let mut decoder = SyncDecoder::new();\n 197│\n 198│    loop {\n    │...\n 344│    let stream_id = opened.stream_id;\n*345│    brain.net_stream_subscribe(stream_id, 0)?;\n 346│\n 347│    let sync_id = format!(\"pull-{}\", open_op.seq);\n 348│    let mut have_tips = BTreeMap::new();\n## wan.rs\n 202│    brain.net_stream_send(opened.stream_id, &ndjson::encode_line(msg), None, true)?;\n*203│    brain.net_stream_subscribe(opened.stream_id, 0)?;\n 204│    let mut decoder = WanReplyDecoder::new();\n 205│    loop {\n 206│        match brain.read_event()? {\n## xfer.rs\n 233│) -> io::Result<XferServeOutcome> {\n*234│    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n 235│    let mut decoder = XferDecoder::new();\n 236│    let mut recv: Option<RecvState> = None;\n 237│\n    │...\n 436│    let stream_id = opened.stream_id;\n*437│    brain.net_stream_subscribe(stream_id, 0)?;\n 438│    let line = XferRecord::Fetch {\n 439│        path: remote_path.to_string(),\n 440│        xfer_id: xfer_id.to_string(),\n    │...\n 546│    let stream_id = opened.stream_id;\n*547│    brain.net_stream_subscribe(stream_id, 0)?;\n 548│    let push = XferRecord::Push {\n 549│        path: remote_path.to_string(),\n 550│        total,"},"isError":false,"timestamp":1784247529619}}
{"type":"message","id":"c28d41f8","parentId":"6a6a5487","timestamp":"2026-07-17T00:18:49.621Z","message":{"role":"toolResult","toolCallId":"call_bf3Z2WI4iIhPSnNbIzHHyrv5|fc_0ceecce325b7fe2d016a5974e927a08198b59f480c6af973f3","toolName":"grep","content":[{"type":"text","text":"# crates/\n\n## spt-daemon/\n\n### src/\n#### brainproc.rs\n 476|    /// brain round-trip): the supervisor promotes a ready candidate only once this is\n*477|    /// `true`, so `brain.ready` arriving inside the W2 `BRAIN_WRITE_DEADLINE` window\n 478|    /// can no longer promote onto a still-frozen control plane (the 22:47 false-\n 479|    /// promote). Fail-safe toward DRAINED (`true`) where there is nothing to read — a\n 480|    /// net-less broker, no hosted sessions, or [`NoTrialEnv`] — so the gate never\n...\n 667|/// NOT a promote: the W2 stall-evict only BOUNDS the false-promote window to\n*668|/// `BRAIN_WRITE_DEADLINE`, so a new brain can signal ready while the OLD generation's\n 669|/// local controller conn is still wedged (the 22:47 apply \"promoted\" onto a frozen\n 670|/// control plane). So readiness is LATCHED (`ready_seen` — a candidate that stamped\n 671|/// ready then exited is not demoted, preserving the ready-then-exit-still-promotes\n...\n 676|/// The normal case drains well inside the window — W2 bounds a real drain to\n*677|/// `BRAIN_WRITE_DEADLINE` (15 s) < [`SUPERVISE_HEALTHY_RUN`] (30 s) — and a cleanly-\n 678|/// closed old conn drains at once. `child.try_wait()` is consulted only while\n 679|/// NOT-yet-ready, so a ready-then-exited candidate still promotes once drained.\n 680|fn run_trial(\n#### broker.rs\n 140|/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n*141|/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n 142|/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n 143|/// never trips the deadline.\n 144|const CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n...\n 153|/// untouched.\n*154|const CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n 155|\n 156|/// How long a controller's writer thread may sit BLOCKED inside a single socket\n 157|/// write — the brain subscriber suspended / black-holed — before a take/reattach\n...\n 159|/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n*160|/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n 161|/// protects the fan-out against ONE per-client conn falling behind and is\n 162|/// output-driven (the drain observes a Full channel); THIS bound is measured\n 163|/// full-with-**zero writer progress** and covers the case the output-driven path\n...\n 171|// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n*172|const BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n 173|\n*174|/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n*175|/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n 176|/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n 177|/// the slot) is provable in ~1 s instead of 15 (mirrors the\n 178|/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n...\n 181|fn brain_write_deadline() -> Duration {\n*182|    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n 183|        Ok(ms) => ms\n 184|            .parse::<u64>()\n 185|            .map(Duration::from_millis)\n*186|            .unwrap_or(BRAIN_WRITE_DEADLINE),\n*187|        Err(_) => BRAIN_WRITE_DEADLINE,\n 188|    }\n 189|}\n 190|\n...\n 285|/// buggy translation binary can therefore stall operator input for at most this\n*286|/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n 287|const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n 288|\n 289|/// The effective commit deadline. Production is the const above; the int gate\n...\n 492|/// advances `delivered_through` on each successful write, and it is never evicted\n*493|/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\n 494|struct ControllerSink {\n 495|    /// The bounded live-frame queue to the controller's writer thread.\n 496|    tx: SyncSender<CtrlMsg>,\n...\n 505|    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n*506|    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n 507|    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n 508|    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n 509|    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n...\n 515|    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n*516|    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n 517|    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n 518|    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n 519|    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n...\n 723|    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n*724|    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n 725|    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n 726|    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n 727|    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n...\n 729|    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n*730|    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n 731|    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n 732|    /// (bounded wedge preserved). The writer still does the blocking socket write +\n 733|    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n...\n 837|                Err(TrySendError::Full(_)) => {\n*838|                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n 839|                        Some(epoch) // stalled past the deadline → bounded detach\n 840|                    } else {\n 841|                        None // drop this chunk; controller falls behind the ring\n...\n 1004|        // writer is BLOCKED (not exited) on a suspended brain conn past\n*1005|        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n 1006|        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n 1007|        // stamp even when nobody take/reattaches. Deferred stamp: the reap runs in\n 1008|        // the multi-session snapshot closure (no I/O under the shared sessions lock).\n...\n 1051|\n*1052|    /// Stall-evict a controller wedged past [`BRAIN_WRITE_DEADLINE`]: clear the slot\n 1053|    /// and re-stamp `driven_by`/`controlled` (the BROKER-side release — no brain\n 1054|    /// round-trip, so `controlled-by` can never latch behind a dead transit) and\n 1055|    /// record the evict for observability (broker log now; W3 persists the stderr,\n...\n 1167|        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller WEDGED\n*1168|        // on a suspended/black-holed brain conn past BRAIN_WRITE_DEADLINE is\n 1169|        // stall-evicted HERE — the take/reattach self-heals against broker truth\n 1170|        // without waiting for new output (the output-driven mark_controller_gone\n 1171|        // never fires on an idle session). Inline stamp: this path already writes the\n...\n 4857|    /// `None`, never blocks), the viewer fan-out still runs, and only a controller Full\n*4858|    /// for `CONTROLLER_WRITE_DEADLINE` since its last success is evicted. (Pre-b4 the\n 4859|    /// drain inline-sleep-polled up to 5s on a full controller, starving the viewer —\n 4860|    /// the warm-forkpty a_journaled wedge.)\n 4861|    // [unit->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\n#### conn.rs\n 74|/// [`BrokerConn::new`]. Before this the only conn identity was `Arc::ptr_eq`,\n*75|/// which does not survive a log line: per-line CONN_WRITE_POISONED churn was\n 76|/// UNDECIDABLE between fresh-carrier churn and stderr interleave (the F-039\n 77|/// RCA's terminal fork). Every lifecycle record carries `conn=<id>`.\n 78|// [impl->REQ-CONN-POISON-ATTRIBUTION]\n...\n 229|    /// Render the once-per-conn retirement record — the F-039 token split\n*230|    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\n 231|    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\n 232|    /// watches); an ORGANIC write failure (peer death / broken pipe on an\n 233|    /// already-dead counterpart, the offline-peer dial churn) emits the\n...\n 242|            format!(\n*243|                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\n 244|                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\n 245|                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n 246|                self.attribution(),\n...\n 287|            // First poison of this conn: loud, once. The token is class-split\n*288|            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\n 289|            // observable), organic fast-fail → CONN_WRITE_RETIRED.\n 290|            eprintln!(\n 291|                \"{}\",\n...\n 424|    /// deadline (gate-wait + OS write completion) — the broker passes its\n*425|    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n 426|    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n 427|        let raw = raw_of(&half);\n 428|        let inner = Arc::new(Inner {\n...\n 707|    // [unit->REQ-CONN-POISON-DIAL-SCOPE] the F-039 leg-(a) token split: the loud\n*708|    // CONN_WRITE_POISONED token is RESERVED for the deadline (timed_out) class;\n 709|    // an organic write failure renders the distinct CONN_WRITE_RETIRED token\n 710|    // carrying the error kind — and (doyle amendment 1) the SAME attribution\n 711|    // fields as POISONED: conn id, accreted role/endpoint/session facts, wall +\n...\n 721|        assert!(\n*722|            poisoned.starts_with(\"CONN_WRITE_POISONED:\"),\n 723|            \"deadline class keeps the loud wedge-observable token: {poisoned}\"\n 724|        );\n 725|        let retired = conn\n#### livehost.rs\n 1016|            // spawns a real release `spt daemon brain` child cfg(test) can't reach — the\n*1017|            // SPT_BRAIN_WRITE_DEADLINE_MS precedent. Still honors `stop` on a tight tick.\n 1018|            // [impl->REQ-UPDATE-TRIAL-DRAIN-DRIVE]\n 1019|            if std::env::var_os(\"SPT_LIVEHOST_RECONCILE_DISABLE\").is_some() {\n 1020|                std::thread::sleep(Duration::from_millis(100));\n\n### tests/\n#### brain_decouple.rs\n 9|//! output-driven eviction (`append` → `mark_controller_gone`, bounded by the 5 s\n*10|//! `CONTROLLER_WRITE_DEADLINE`) fires only on NEW output, and `reap_dead_controller`\n 11|//! only caught an EXITED writer — so a controller BLOCKED on an idle session was\n 12|//! evicted by neither: control latched, reattach was refused, `rc --take` hung,\n 13|//! `daemon status` still read healthy.\n...\n 21|//! (a peer that stopped draining). This is the same analog the 7.12 wedge gate uses.\n*22|//! `SPT_BRAIN_WRITE_DEADLINE_MS` shrinks the 15 s production deadline so the\n 23|//! structural outcome is provable in ~1 s (the deadline knob grants wall-clock only;\n 24|//! it never relaxes an assertion).\n 25|//!\n...\n 46|//!\n*47|//! Shares ONE process-global `SPT_HOME` + the `SPT_BRAIN_WRITE_DEADLINE_MS` env, and\n 48|//! spins a REAL broker + PTY child. Run isolated with\n 49|//! `cargo nextest run --test brain_decouple` (process-per-test — CI's path) or\n 50|//! `cargo test --test brain_decouple -- --test-threads=1`. Never a bare\n...\n 225|    // instead of 15 s — the structural outcome, fast + deterministic. Well under the\n*226|    // 5 s output-driven CONTROLLER_WRITE_DEADLINE, so ONLY the W2 time-based\n 227|    // stall-evict can free the slot in the window this test acts in.\n*228|    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n 229|\n 230|    let name = unique_name();\n 231|    let broker = Broker::bind(&name).expect(\"bind broker\");\n...\n 476|    // Shrink the brain-write deadline so the wedged writer is \"stalled\" in ~400 ms.\n*477|    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n 478|\n 479|    let name = unique_name();\n 480|    let broker = Broker::bind(&name).expect(\"bind broker\");\n#### conn_blackhole_lifecycle.rs\n 70|//!\n*71|//! Shares ONE process-global `SPT_HOME` + `SPT_BRAIN_WRITE_DEADLINE_MS`, spins a\n 72|//! REAL broker + TWO PTY children. HEAVY class. Run isolated with\n 73|//! `cargo nextest run -p spt-daemon --test conn_blackhole_lifecycle` (CI's path)\n 74|//! or `cargo test --test conn_blackhole_lifecycle -- --test-threads=1`. NEVER a\n...\n 253|    // grants wall-clock only; it never relaxes an assertion).\n*254|    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n 255|\n 256|    let name = unique_name();\n 257|    let broker = Broker::bind(&name).expect(\"bind broker\");\n#### false_promote.rs\n 8|//! W2's stall-evict (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) only BOUNDS the false-\n*9|//! promote window to `BRAIN_WRITE_DEADLINE` (15 s) — it does NOT close it. A new brain\n 10|//! can signal `brain.ready` INSIDE that window while the OLD generation's local\n 11|//! controller conn is still wedged (blocked writes pending), so `update apply`\n 12|//! \"promotes\" onto a still-frozen control plane (the incident-night 22:47 apply). The\n...\n 33|//! rides the REAL logical path (the take's `stall_evict_controller`), unchanged.\n*34|//! `SPT_BRAIN_WRITE_DEADLINE_MS` still shrinks the 15 s deadline (the knob grants\n 35|//! wall-clock only; it never relaxes an assertion).\n 36|//!\n 37|//! ## What this rig asserts (the DRAINED invariant)\n...\n 48|//!\n*49|//! Shares ONE process-global `SPT_HOME` + the `SPT_BRAIN_WRITE_DEADLINE_MS` env and\n 50|//! spins a REAL broker + PTY child. Run isolated with\n 51|//! `cargo nextest run --test false_promote` (process-per-test — CI's path) or\n 52|//! `cargo test --test false_promote -- --test-threads=1`. Never a bare\n...\n 240|    // ~400 ms instead of 15 s (the structural outcome, fast + deterministic).\n*241|    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n 242|\n 243|    let name = unique_name();\n 244|    let broker = Broker::bind(&name).expect(\"bind broker\");\n#### inject_control_wedge.rs\n 23|//! bounded `send_timeout` OFF the log lock; a fully-stalled controller is detached\n*24|//! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed\n 25|//! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test\n 26|//! asserts that fixed behavior.\n 27|//!\n...\n 325|    //    while the controller is fully backed up. Probe DURING the stall (before\n*326|    //    CONTROLLER_WRITE_DEADLINE = 5s elapses). ──\n 327|    thread::sleep(Duration::from_secs(3));\n 328|\n 329|    // A fresh connection's KIND_NET_STATUS touches neither the sessions nor the\n...\n 2162|//     controller sink stays OPEN (no clean EOF/detach) and the session is IDLE\n*2163|//     (no output → the W1 drain-evict / CONTROLLER_WRITE_DEADLINE never fires) →\n 2164|//     the slot stays Some(origin) → `controller_by` reads SOME, `driven_by` stays\n 2165|//     Some — the broker still THINKS it's controlled.\n 2166|//\n...\n 2317|    // Spawn a QUIET long-lived session for the endpoint: it produces NO output, so\n*2318|    // the session is IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE\n 2319|    // never trips AND `controller_writer` never writes to the abandoned operator →\n 2320|    // no Disconnected-evict). It never exits, so the session row stays live for the\n 2321|    // read with NO mid-read kill — which also removes the Windows reap-during-read\n\n## spt/tests/\n### resume_no_control_steal_e2e.rs\n 343|    // reconcile loop so the boot is a clean, isolated real brain.\n*344|    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"800\");\n 345|    std::env::set_var(\"SPT_LIVEHOST_RECONCILE_DISABLE\", \"1\");\n 346|\n 347|    let spt_bin = PathBuf::from(env!(\"CARGO_BIN_EXE_spt\"));\n### trial_drain_drive_e2e.rs\n 45|//! reads only `by.is_none()` + `write_blocked_since`, so it cannot tell the two apart\n*46|//! (doyle FINALIZE). `SPT_BRAIN_WRITE_DEADLINE_MS` shrinks the 15s maturity to ~800ms\n 47|//! (the knob grants wall-clock only — it never relaxes an assertion); the deadline is\n 48|//! read BROKER-side, in THIS process's in-proc broker where the reap + the drained read\n 49|//! both run.\n...\n 137|/// `write_blocked_since` (the stall-evict/reap signal). Then the child is SILENT, so no\n*138|/// further output reaches the drain: the ORTHOGONAL, output-driven `CONTROLLER_WRITE_DEADLINE`\n 139|/// detach (5s, tripped only by a channel that stays FULL as new chunks arrive) never fires,\n*140|/// and the wedge PERSISTS until the `BRAIN_WRITE_DEADLINE` reap (the fix's heartbeat drive)\n 141|/// clears it. This is the FAITHFUL analog of the field's idle-brain black-hole (broker.rs\n*142|/// BRAIN_WRITE_DEADLINE doc): a suspended brain conn with a stuck write on an idle session.\n 143|/// A CONTINUOUS flood (false_promote.rs) instead trips the 5s output-driven detach and only\n 144|/// works because that rig finishes < 5s — the slower subprocess rig would self-heal.\n 145|fn burst_then_idle_spawn_req(endpoint: &str) -> SpawnReq {\n...\n 339|    // the drained read both run here). The knob grants wall-clock only.\n*340|    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"800\");\n 341|    // Silence the subprocess brain's livehost reconcile loop (inherited by the child).\n 342|    // livehost's own `brain.sessions()` poll (every 5s) would OTHERWISE drive the same\n 343|    // broker reap and MASK the fix — reproducing the field's livehost-silent trial"}],"details":{"scopePath":"crates","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":53,"fileCount":10,"files":["crates/spt-daemon/src/brainproc.rs","crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/conn.rs","crates/spt-daemon/src/livehost.rs","crates/spt-daemon/tests/brain_decouple.rs","crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","crates/spt-daemon/tests/false_promote.rs","crates/spt-daemon/tests/inject_control_wedge.rs","crates/spt/tests/resume_no_control_steal_e2e.rs","crates/spt/tests/trial_drain_drive_e2e.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/brainproc.rs","count":3},{"path":"crates/spt-daemon/src/broker.rs","count":20},{"path":"crates/spt-daemon/src/conn.rs","count":7},{"path":"crates/spt-daemon/src/livehost.rs","count":1},{"path":"crates/spt-daemon/tests/brain_decouple.rs","count":6},{"path":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","count":2},{"path":"crates/spt-daemon/tests/false_promote.rs","count":4},{"path":"crates/spt-daemon/tests/inject_control_wedge.rs","count":4},{"path":"crates/spt/tests/resume_no_control_steal_e2e.rs","count":1},{"path":"crates/spt/tests/trial_drain_drive_e2e.rs","count":5}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/\n\n## spt-daemon/\n\n### src/\n#### brainproc.rs\n 476│    /// brain round-trip): the supervisor promotes a ready candidate only once this is\n*477│    /// `true`, so `brain.ready` arriving inside the W2 `BRAIN_WRITE_DEADLINE` window\n 478│    /// can no longer promote onto a still-frozen control plane (the 22:47 false-\n 479│    /// promote). Fail-safe toward DRAINED (`true`) where there is nothing to read — a\n 480│    /// net-less broker, no hosted sessions, or [`NoTrialEnv`] — so the gate never\n    │...\n 667│/// NOT a promote: the W2 stall-evict only BOUNDS the false-promote window to\n*668│/// `BRAIN_WRITE_DEADLINE`, so a new brain can signal ready while the OLD generation's\n 669│/// local controller conn is still wedged (the 22:47 apply \"promoted\" onto a frozen\n 670│/// control plane). So readiness is LATCHED (`ready_seen` — a candidate that stamped\n 671│/// ready then exited is not demoted, preserving the ready-then-exit-still-promotes\n    │...\n 676│/// The normal case drains well inside the window — W2 bounds a real drain to\n*677│/// `BRAIN_WRITE_DEADLINE` (15 s) < [`SUPERVISE_HEALTHY_RUN`] (30 s) — and a cleanly-\n 678│/// closed old conn drains at once. `child.try_wait()` is consulted only while\n 679│/// NOT-yet-ready, so a ready-then-exited candidate still promotes once drained.\n 680│fn run_trial(\n#### broker.rs\n  140│/// merely-slow controller DROPS chunks + falls behind the ring, and only a channel\n *141│/// that stays full for [`CONTROLLER_WRITE_DEADLINE`] (tracked via\n  142│/// [`ControllerSink::last_ok`]) is detached. Generous so a momentary slow consumer\n  143│/// never trips the deadline.\n  144│const CONTROLLER_CHANNEL_DEPTH: usize = 4096;\n     │...\n  153│/// untouched.\n *154│const CONTROLLER_WRITE_DEADLINE: Duration = Duration::from_secs(5);\n  155│\n  156│/// How long a controller's writer thread may sit BLOCKED inside a single socket\n  157│/// write — the brain subscriber suspended / black-holed — before a take/reattach\n     │...\n  159│/// REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE). Distinct from and 3× longer than\n *160│/// [`CONTROLLER_WRITE_DEADLINE`] on PURPOSE (doyle ruling Q1): that 5 s bound\n  161│/// protects the fan-out against ONE per-client conn falling behind and is\n  162│/// output-driven (the drain observes a Full channel); THIS bound is measured\n  163│/// full-with-**zero writer progress** and covers the case the output-driven path\n     │...\n  171│// [impl->REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE]\n *172│const BRAIN_WRITE_DEADLINE: Duration = Duration::from_secs(15);\n  173│\n *174│/// The EFFECTIVE brain-write deadline. Production is [`BRAIN_WRITE_DEADLINE`]; the\n *175│/// suspend-brain int rig shrinks it via `SPT_BRAIN_WRITE_DEADLINE_MS` so the\n  176│/// structural outcome (a wedged controller is stall-evicted → take/reattach frees\n  177│/// the slot) is provable in ~1 s instead of 15 (mirrors the\n  178│/// `SPT_INJECT_COMMIT_DEADLINE_MS` env-knob precedent). Grants test wall-clock\n     │...\n  181│fn brain_write_deadline() -> Duration {\n *182│    match std::env::var(\"SPT_BRAIN_WRITE_DEADLINE_MS\") {\n  183│        Ok(ms) => ms\n  184│            .parse::<u64>()\n  185│            .map(Duration::from_millis)\n *186│            .unwrap_or(BRAIN_WRITE_DEADLINE),\n *187│        Err(_) => BRAIN_WRITE_DEADLINE,\n  188│    }\n  189│}\n  190│\n     │...\n  285│/// buggy translation binary can therefore stall operator input for at most this\n *286│/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).\n  287│const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);\n  288│\n  289│/// The effective commit deadline. Production is the const above; the int gate\n     │...\n  492│/// advances `delivered_through` on each successful write, and it is never evicted\n *493│/// on a single full `try_send` — only after [`CONTROLLER_WRITE_DEADLINE`].\n  494│struct ControllerSink {\n  495│    /// The bounded live-frame queue to the controller's writer thread.\n  496│    tx: SyncSender<CtrlMsg>,\n     │...\n  505│    /// The drain does a SINGLE non-blocking `try_send` per chunk and NEVER sleeps;\n *506│    /// a channel that stays Full for [`CONTROLLER_WRITE_DEADLINE`] PAST this stamp\n  507│    /// evicts the stalled controller (bounded wedge preserved), while a slow-but-\n  508│    /// alive controller just drops the chunk + falls behind the ring (resume-from-\n  509│    /// floor). Seeded `now` at [`OutputLog::become_controller`].\n     │...\n  515│    /// ([`OutputLog::controller_stalled`]) sees a `Some` older than\n *516│    /// [`BRAIN_WRITE_DEADLINE`] as a writer WEDGED on a suspended/black-holed brain\n  517│    /// conn and stall-evicts it — the signal `last_ok` (channel-handoff time) cannot\n  518│    /// give: an IDLE controller (writer parked on `rx.recv()`, `None` here) and a\n  519│    /// BLOCKED one (writer stuck in the socket write, `Some` here) both leave\n     │...\n  723│    /// left the HANDOFF as an inline sleep-poll (`ControllerJob::deliver`, up to\n *724│    /// `CONTROLLER_WRITE_DEADLINE`) ON the drain — so a controller draining slower\n  725│    /// than the PTY floods filled its channel, the inline poll throttled the drain,\n  726│    /// and the viewer fan-out below stopped running → a concurrent `rc --view`\n  727│    /// VIEWER starved. Now the controller handoff is ONE non-blocking `try_send`\n     │...\n  729│    /// controller speed. A slow-but-alive controller DROPS the chunk + falls behind\n *730│    /// the ring (resume-from-floor); a controller Full for `CONTROLLER_WRITE_DEADLINE`\n  731│    /// since its last successful handoff (`ControllerSink::last_ok`) is evicted\n  732│    /// (bounded wedge preserved). The writer still does the blocking socket write +\n  733│    /// advances the authoritative CONTIGUOUS cursor. Called only from the session's\n     │...\n  837│                Err(TrySendError::Full(_)) => {\n *838│                    if now.duration_since(c.last_ok) >= CONTROLLER_WRITE_DEADLINE {\n  839│                        Some(epoch) // stalled past the deadline → bounded detach\n  840│                    } else {\n  841│                        None // drop this chunk; controller falls behind the ring\n     │...\n 1004│        // writer is BLOCKED (not exited) on a suspended brain conn past\n*1005│        // BRAIN_WRITE_DEADLINE is also reaped here, so the honest `KIND_SESSIONS`\n 1006│        // truth + the off-lock converge_perch_stamps release its stale `driven_by`\n 1007│        // stamp even when nobody take/reattaches. Deferred stamp: the reap runs in\n 1008│        // the multi-session snapshot closure (no I/O under the shared sessions lock).\n     │...\n 1051│\n*1052│    /// Stall-evict a controller wedged past [`BRAIN_WRITE_DEADLINE`]: clear the slot\n 1053│    /// and re-stamp `driven_by`/`controlled` (the BROKER-side release — no brain\n 1054│    /// round-trip, so `controlled-by` can never latch behind a dead transit) and\n 1055│    /// record the evict for observability (broker log now; W3 persists the stderr,\n     │...\n 1167│        // W2 decouple (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE): a controller WEDGED\n*1168│        // on a suspended/black-holed brain conn past BRAIN_WRITE_DEADLINE is\n 1169│        // stall-evicted HERE — the take/reattach self-heals against broker truth\n 1170│        // without waiting for new output (the output-driven mark_controller_gone\n 1171│        // never fires on an idle session). Inline stamp: this path already writes the\n     │...\n 4857│    /// `None`, never blocks), the viewer fan-out still runs, and only a controller Full\n*4858│    /// for `CONTROLLER_WRITE_DEADLINE` since its last success is evicted. (Pre-b4 the\n 4859│    /// drain inline-sleep-polled up to 5s on a full controller, starving the viewer —\n 4860│    /// the warm-forkpty a_journaled wedge.)\n 4861│    // [unit->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]\n#### conn.rs\n  74│/// [`BrokerConn::new`]. Before this the only conn identity was `Arc::ptr_eq`,\n *75│/// which does not survive a log line: per-line CONN_WRITE_POISONED churn was\n  76│/// UNDECIDABLE between fresh-carrier churn and stderr interleave (the F-039\n  77│/// RCA's terminal fork). Every lifecycle record carries `conn=<id>`.\n  78│// [impl->REQ-CONN-POISON-ATTRIBUTION]\n    │...\n 229│    /// Render the once-per-conn retirement record — the F-039 token split\n*230│    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\n 231│    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\n 232│    /// watches); an ORGANIC write failure (peer death / broken pipe on an\n 233│    /// already-dead counterpart, the offline-peer dial churn) emits the\n    │...\n 242│            format!(\n*243│                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\n 244│                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\n 245│                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n 246│                self.attribution(),\n    │...\n 287│            // First poison of this conn: loud, once. The token is class-split\n*288│            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\n 289│            // observable), organic fast-fail → CONN_WRITE_RETIRED.\n 290│            eprintln!(\n 291│                \"{}\",\n    │...\n 424│    /// deadline (gate-wait + OS write completion) — the broker passes its\n*425│    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n 426│    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n 427│        let raw = raw_of(&half);\n 428│        let inner = Arc::new(Inner {\n    │...\n 707│    // [unit->REQ-CONN-POISON-DIAL-SCOPE] the F-039 leg-(a) token split: the loud\n*708│    // CONN_WRITE_POISONED token is RESERVED for the deadline (timed_out) class;\n 709│    // an organic write failure renders the distinct CONN_WRITE_RETIRED token\n 710│    // carrying the error kind — and (doyle amendment 1) the SAME attribution\n 711│    // fields as POISONED: conn id, accreted role/endpoint/session facts, wall +\n    │...\n 721│        assert!(\n*722│            poisoned.starts_with(\"CONN_WRITE_POISONED:\"),\n 723│            \"deadline class keeps the loud wedge-observable token: {poisoned}\"\n 724│        );\n 725│        let retired = conn\n#### livehost.rs\n 1016│            // spawns a real release `spt daemon brain` child cfg(test) can't reach — the\n*1017│            // SPT_BRAIN_WRITE_DEADLINE_MS precedent. Still honors `stop` on a tight tick.\n 1018│            // [impl->REQ-UPDATE-TRIAL-DRAIN-DRIVE]\n 1019│            if std::env::var_os(\"SPT_LIVEHOST_RECONCILE_DISABLE\").is_some() {\n 1020│                std::thread::sleep(Duration::from_millis(100));\n\n### tests/\n#### brain_decouple.rs\n   9│//! output-driven eviction (`append` → `mark_controller_gone`, bounded by the 5 s\n *10│//! `CONTROLLER_WRITE_DEADLINE`) fires only on NEW output, and `reap_dead_controller`\n  11│//! only caught an EXITED writer — so a controller BLOCKED on an idle session was\n  12│//! evicted by neither: control latched, reattach was refused, `rc --take` hung,\n  13│//! `daemon status` still read healthy.\n    │...\n  21│//! (a peer that stopped draining). This is the same analog the 7.12 wedge gate uses.\n *22│//! `SPT_BRAIN_WRITE_DEADLINE_MS` shrinks the 15 s production deadline so the\n  23│//! structural outcome is provable in ~1 s (the deadline knob grants wall-clock only;\n  24│//! it never relaxes an assertion).\n  25│//!\n    │...\n  46│//!\n *47│//! Shares ONE process-global `SPT_HOME` + the `SPT_BRAIN_WRITE_DEADLINE_MS` env, and\n  48│//! spins a REAL broker + PTY child. Run isolated with\n  49│//! `cargo nextest run --test brain_decouple` (process-per-test — CI's path) or\n  50│//! `cargo test --test brain_decouple -- --test-threads=1`. Never a bare\n    │...\n 225│    // instead of 15 s — the structural outcome, fast + deterministic. Well under the\n*226│    // 5 s output-driven CONTROLLER_WRITE_DEADLINE, so ONLY the W2 time-based\n 227│    // stall-evict can free the slot in the window this test acts in.\n*228│    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n 229│\n 230│    let name = unique_name();\n 231│    let broker = Broker::bind(&name).expect(\"bind broker\");\n    │...\n 476│    // Shrink the brain-write deadline so the wedged writer is \"stalled\" in ~400 ms.\n*477│    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n 478│\n 479│    let name = unique_name();\n 480│    let broker = Broker::bind(&name).expect(\"bind broker\");\n#### conn_blackhole_lifecycle.rs\n  70│//!\n *71│//! Shares ONE process-global `SPT_HOME` + `SPT_BRAIN_WRITE_DEADLINE_MS`, spins a\n  72│//! REAL broker + TWO PTY children. HEAVY class. Run isolated with\n  73│//! `cargo nextest run -p spt-daemon --test conn_blackhole_lifecycle` (CI's path)\n  74│//! or `cargo test --test conn_blackhole_lifecycle -- --test-threads=1`. NEVER a\n    │...\n 253│    // grants wall-clock only; it never relaxes an assertion).\n*254│    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n 255│\n 256│    let name = unique_name();\n 257│    let broker = Broker::bind(&name).expect(\"bind broker\");\n#### false_promote.rs\n   8│//! W2's stall-evict (REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE) only BOUNDS the false-\n  *9│//! promote window to `BRAIN_WRITE_DEADLINE` (15 s) — it does NOT close it. A new brain\n  10│//! can signal `brain.ready` INSIDE that window while the OLD generation's local\n  11│//! controller conn is still wedged (blocked writes pending), so `update apply`\n  12│//! \"promotes\" onto a still-frozen control plane (the incident-night 22:47 apply). The\n    │...\n  33│//! rides the REAL logical path (the take's `stall_evict_controller`), unchanged.\n *34│//! `SPT_BRAIN_WRITE_DEADLINE_MS` still shrinks the 15 s deadline (the knob grants\n  35│//! wall-clock only; it never relaxes an assertion).\n  36│//!\n  37│//! ## What this rig asserts (the DRAINED invariant)\n    │...\n  48│//!\n *49│//! Shares ONE process-global `SPT_HOME` + the `SPT_BRAIN_WRITE_DEADLINE_MS` env and\n  50│//! spins a REAL broker + PTY child. Run isolated with\n  51│//! `cargo nextest run --test false_promote` (process-per-test — CI's path) or\n  52│//! `cargo test --test false_promote -- --test-threads=1`. Never a bare\n    │...\n 240│    // ~400 ms instead of 15 s (the structural outcome, fast + deterministic).\n*241│    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"400\");\n 242│\n 243│    let name = unique_name();\n 244│    let broker = Broker::bind(&name).expect(\"bind broker\");\n#### inject_control_wedge.rs\n   23│//! bounded `send_timeout` OFF the log lock; a fully-stalled controller is detached\n  *24│//! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed\n   25│//! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test\n   26│//! asserts that fixed behavior.\n   27│//!\n     │...\n  325│    //    while the controller is fully backed up. Probe DURING the stall (before\n *326│    //    CONTROLLER_WRITE_DEADLINE = 5s elapses). ──\n  327│    thread::sleep(Duration::from_secs(3));\n  328│\n  329│    // A fresh connection's KIND_NET_STATUS touches neither the sessions nor the\n     │...\n 2162│//     controller sink stays OPEN (no clean EOF/detach) and the session is IDLE\n*2163│//     (no output → the W1 drain-evict / CONTROLLER_WRITE_DEADLINE never fires) →\n 2164│//     the slot stays Some(origin) → `controller_by` reads SOME, `driven_by` stays\n 2165│//     Some — the broker still THINKS it's controlled.\n 2166│//\n     │...\n 2317│    // Spawn a QUIET long-lived session for the endpoint: it produces NO output, so\n*2318│    // the session is IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE\n 2319│    // never trips AND `controller_writer` never writes to the abandoned operator →\n 2320│    // no Disconnected-evict). It never exits, so the session row stays live for the\n 2321│    // read with NO mid-read kill — which also removes the Windows reap-during-read\n\n## spt/tests/\n### resume_no_control_steal_e2e.rs\n 343│    // reconcile loop so the boot is a clean, isolated real brain.\n*344│    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"800\");\n 345│    std::env::set_var(\"SPT_LIVEHOST_RECONCILE_DISABLE\", \"1\");\n 346│\n 347│    let spt_bin = PathBuf::from(env!(\"CARGO_BIN_EXE_spt\"));\n### trial_drain_drive_e2e.rs\n  45│//! reads only `by.is_none()` + `write_blocked_since`, so it cannot tell the two apart\n *46│//! (doyle FINALIZE). `SPT_BRAIN_WRITE_DEADLINE_MS` shrinks the 15s maturity to ~800ms\n  47│//! (the knob grants wall-clock only — it never relaxes an assertion); the deadline is\n  48│//! read BROKER-side, in THIS process's in-proc broker where the reap + the drained read\n  49│//! both run.\n    │...\n 137│/// `write_blocked_since` (the stall-evict/reap signal). Then the child is SILENT, so no\n*138│/// further output reaches the drain: the ORTHOGONAL, output-driven `CONTROLLER_WRITE_DEADLINE`\n 139│/// detach (5s, tripped only by a channel that stays FULL as new chunks arrive) never fires,\n*140│/// and the wedge PERSISTS until the `BRAIN_WRITE_DEADLINE` reap (the fix's heartbeat drive)\n 141│/// clears it. This is the FAITHFUL analog of the field's idle-brain black-hole (broker.rs\n*142│/// BRAIN_WRITE_DEADLINE doc): a suspended brain conn with a stuck write on an idle session.\n 143│/// A CONTINUOUS flood (false_promote.rs) instead trips the 5s output-driven detach and only\n 144│/// works because that rig finishes < 5s — the slower subprocess rig would self-heal.\n 145│fn burst_then_idle_spawn_req(endpoint: &str) -> SpawnReq {\n    │...\n 339│    // the drained read both run here). The knob grants wall-clock only.\n*340│    std::env::set_var(\"SPT_BRAIN_WRITE_DEADLINE_MS\", \"800\");\n 341│    // Silence the subprocess brain's livehost reconcile loop (inherited by the child).\n 342│    // livehost's own `brain.sessions()` poll (every 5s) would OTHERWISE drive the same\n 343│    // broker reap and MASK the fix — reproducing the field's livehost-silent trial"},"isError":false,"timestamp":1784247529621}}
{"type":"message","id":"d19496b2","parentId":"c28d41f8","timestamp":"2026-07-17T00:18:54.603Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Assessing conn.rs timeout handling**"},{"type":"toolCall","id":"call_doqhnpWgxeURTtApbbyzBVlq|fc_0ceecce325b7fe2d016a5974ee333881989230c685df8ed85c","name":"read","arguments":{"i":"Reading writer timeout internals","path":"crates/spt-daemon/src/conn.rs","selector":"180-460"}},{"type":"toolCall","id":"call_ec5pDJNIbQ7kptrvvLAanhkS|fc_0ceecce325b7fe2d016a5974ee3574819886cbe9327a467850","name":"read","arguments":{"i":"Reading stream attach replay","path":"crates/spt-daemon/src/nethost.rs","selector":"285-365"}},{"type":"toolCall","id":"call_pdNuZGF5vON8mrbSakgIN6Y0|fc_0ceecce325b7fe2d016a5974ee3584819885d1d761e050300b","name":"read","arguments":{"i":"Reading dispatcher worker lifecycle","path":"crates/spt-daemon/src/dispatch.rs","selector":"299-375"}},{"type":"toolCall","id":"call_jq5TmRg8YqIWZc16muP1S42m|fc_0ceecce325b7fe2d016a5974ee358c81989642f2ffc7eb8ce4","name":"read","arguments":{"i":"Reading stream classification blocking","path":"crates/spt-daemon/src/dispatch.rs","selector":"410-465"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":11522,"output":184,"cacheRead":38400,"cacheWrite":0,"totalTokens":50106,"reasoningTokens":20,"cost":{"input":0.05761,"output":0.00552,"cacheRead":0.0192,"cacheWrite":0,"total":0.08233}},"stopReason":"toolUse","timestamp":1784247529629,"responseId":"resp_0ceecce325b7fe2d016a5974e9eda481988751a39232b684d5","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5974eacd64819892862a5978b3615d","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXTuTYEVq_qHWxilBRz8c82EZCLdyZpchCZTGY5WGX7HnMa2gAilfv1jIAS4B7hU08aL9YJysyUf2htpgYwFxuXNz7JZsb55Vxc57-drfNnBdfEuMfQURz917Wor1LNlxRuSxbUmWD4NJU83Y7jjH7laIqQB48LaXsIaGRUfv2ikKpyg3Scakz25H8u0dT9mGT1t3blx9S3Zrbroz7T2kBp4-ELX9bwZBAm5KcI8P5WbAPqUfuiakNM7ISdQ5OtgZTpvOTbWzAdxvCTGkhdDmFlTY2QNDmpGSVRX8y83aJPogAHWKiyagjjzSyn7g6jj_6EhPKdpw40v-8oV28m68XjwtKF03j-2fOf1DcjxbGSB0qxkJh0NNhN3UHfUtQFVvscFomvG0BuT2dcJRXKMu5Cs1XkP2ptMzYM2MSqN9qEvxwi-i5lboRnfG4Y03UksRDIlxGJEHw0YDrQQS2CyglK2amOUmNHPpGA7kAYCml8SbduH6omeG-ZOwizvBdhOB9FwH5hb1ORnvok7yJssqnyN6WL2LIA3PSND9emARTAkuE_QzzxZKJmGI3oJBAMIKBgMgP6YJHEKlJF04lOGtKy8Ed4pdtvbrt9FaOxOB3VJ5CMaj3nZY4HU76EahaGdj9GJM_tn6I-hEk62KkA9DP6rvScc2KtriuYmxT-k_4wumCRgT48mEiD9vAWXIfEIiKBGhe0wylKSZhE6_ZrN5G4ayqvcs7_8qpsmEUwyUBQ4OnTdKexQ7JV9sQi4IrtTA1ap7SEP3iHKNPXZzIIzIfdIAEb_EHJxZn57KSEv3smCrAJJesiZne97awMPKH0FQzhngNGlJuntJOKVowWLc009S2i12wFws6DiR7iUTF7Nvpc4IqZBO9xSYBWrwWQ-cZ9sieB5QmointkzZxyNGPGxgPsUDYmhlLYJIC58ZHI1xlRZr3y8m3HPEoe_Y8QO1M4SfjFYjcwb5lYY1A8vOcZX3Q-9oujBO8JI5tSD7JOLguokNFlsyYACYYz5SEfW_l9SjIODdEQU3PC5VRWm9k7cmFSqtBUKYrpS8dbo5FM1LXY=","summary":[{"type":"summary_text","text":"**Assessing conn.rs timeout handling**"}]},{"id":"fc_0ceecce325b7fe2d016a5974ee333881989230c685df8ed85c","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading writer timeout internals\",\"path\":\"crates/spt-daemon/src/conn.rs\",\"selector\":\"180-460\"}","call_id":"call_doqhnpWgxeURTtApbbyzBVlq","name":"read"},{"id":"fc_0ceecce325b7fe2d016a5974ee3574819886cbe9327a467850","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream attach replay\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"285-365\"}","call_id":"call_ec5pDJNIbQ7kptrvvLAanhkS","name":"read"},{"id":"fc_0ceecce325b7fe2d016a5974ee3584819885d1d761e050300b","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading dispatcher worker lifecycle\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"299-375\"}","call_id":"call_pdNuZGF5vON8mrbSakgIN6Y0","name":"read"},{"id":"fc_0ceecce325b7fe2d016a5974ee358c81989642f2ffc7eb8ce4","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream classification blocking\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"410-465\"}","call_id":"call_jq5TmRg8YqIWZc16muP1S42m","name":"read"}]},"duration":4964.539200000465,"ttft":1175.443400003016,"contextSnapshot":{"promptTokens":49922,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_doqhnpWgxeURTtApbbyzBVlq|fc_0ceecce325b7fe2d016a5974ee333881989230c685df8ed85c","toolName":"read","startedAt":"2026-07-17T00:18:54.604Z","args":{"path":"crates/spt-daemon/src/conn.rs"},"intent":"Reading writer timeout internals"},"id":"2c6cc333","parentId":"d19496b2","timestamp":"2026-07-17T00:18:54.604Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_ec5pDJNIbQ7kptrvvLAanhkS|fc_0ceecce325b7fe2d016a5974ee3574819886cbe9327a467850","toolName":"read","startedAt":"2026-07-17T00:18:54.604Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading stream attach replay"},"id":"c77a2829","parentId":"2c6cc333","timestamp":"2026-07-17T00:18:54.604Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_pdNuZGF5vON8mrbSakgIN6Y0|fc_0ceecce325b7fe2d016a5974ee3584819885d1d761e050300b","toolName":"read","startedAt":"2026-07-17T00:18:54.604Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading dispatcher worker lifecycle"},"id":"f9f10d77","parentId":"c77a2829","timestamp":"2026-07-17T00:18:54.604Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_jq5TmRg8YqIWZc16muP1S42m|fc_0ceecce325b7fe2d016a5974ee358c81989642f2ffc7eb8ce4","toolName":"read","startedAt":"2026-07-17T00:18:54.604Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading stream classification blocking"},"id":"ae9e75a6","parentId":"f9f10d77","timestamp":"2026-07-17T00:18:54.604Z"}
{"type":"message","id":"b3ff0d11","parentId":"ae9e75a6","timestamp":"2026-07-17T00:18:54.620Z","message":{"role":"toolResult","toolCallId":"call_doqhnpWgxeURTtApbbyzBVlq|fc_0ceecce325b7fe2d016a5974ee333881989230c685df8ed85c","toolName":"read","content":[{"type":"text","text":"struct Inner {\n…\n    raw: isize,\n    #[cfg(unix)]\n    raw: i32,\n}\n\n/// Recover a mutex guard even if a holder panicked — the state these guards\n/// protect is a plain slot/flag pair, valid on every panic boundary.\nfn recover<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {\n    match m.lock() {\n        Ok(g) => g,\n        Err(p) => p.into_inner(),\n    }\n}\n\n#[cfg(windows)]\nfn raw_of(half: &SendHalf) -> isize {\n    use std::os::windows::io::{AsHandle, AsRawHandle};\n    match half {\n        SendHalf::NamedPipe(h) => h.as_handle().as_raw_handle() as isize,\n    }\n}\n\n#[cfg(unix)]\nfn raw_of(half: &SendHalf) -> i32 {\n    use std::os::fd::{AsFd, AsRawFd};\n    match half {\n        SendHalf::UdSocket(h) => h.as_fd().as_raw_fd(),\n    }\n}\n\n#[cfg(windows)]\nmod win {\n    // kernel32 is in the MSVC default link set — raw externs, no new dep\n    // (the deelevate.rs / daemon.rs posture).\n    extern \"system\" {\n        pub fn CancelIoEx(handle: isize, overlapped: *mut core::ffi::c_void) -> i32;\n        pub fn DisconnectNamedPipe(handle: isize) -> i32;\n    }\n}\n\nimpl Inner {\n    /// The attribution snapshot every lifecycle record carries: stable conn id,\n    /// accreted role/endpoint/session facts, wall + monotonic stamps.\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    fn attribution(&self) -> String {\n        let label = recover(&self.label);\n        let facts: &str = if label.is_empty() { \"role=unattributed\" } else { &label };\n        format!(\"conn={} {} {}\", self.id, facts, log_stamp())\n    }\n\n    /// Render the once-per-conn retirement record — the F-039 token split\n    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\n    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\n    /// watches); an ORGANIC write failure (peer death / broken pipe on an\n    /// already-dead counterpart, the offline-peer dial churn) emits the\n    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\n    /// SAME attribution fields — quieter class, never thinner (the retired\n    /// class is what discriminates fresh-carrier churn from interleave\n    /// artifact in the field). Behavior is identical either way: poison,\n    /// abort, retire, never reuse.\n    // [impl->REQ-CONN-POISON-DIAL-SCOPE]\n    fn render_retirement(&self, timed_out: bool, cause: Option<&io::Error>) -> String {\n        if timed_out {\n            format!(\n                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\n                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\n                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\n                self.attribution(),\n                self.bound.as_millis()\n            )\n        } else {\n            let cause = match cause {\n                Some(e) => format!(\"{:?}: {e}\", e.kind()),\n                None => \"write failed\".to_string(),\n            };\n            format!(\n                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\n                 broker conn poisoned + retired, never reused (same retire behavior \\\n                 as POISONED — only the failure class differs) \\\n                 [REQ-CONN-POISON-DIAL-SCOPE]\",\n                self.attribution()\n            )\n        }\n    }\n\n    /// One BOUNDED per-conn lifecycle record (leg d, doyle-confirmed\n    /// UNCONDITIONAL): `write-start` (first write only), `transport-close`\n    /// (drop), `writer-exit` / `*-attach` / `*-replaced` (emitted by the\n    /// broker's sink machinery through [`BrokerConn::lifecycle_event`]). The\n    /// timeout/cancel leg is the retirement record itself.\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    fn lifecycle(&self, event: &str, extra: &str) {\n        let sep = if extra.is_empty() { \"\" } else { \" \" };\n        eprintln!(\n            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\n            self.attribution()\n        );\n    }\n\n    /// Abort the connection's in-flight I/O OUT OF BAND (both directions —\n    /// the halves share one OS object) and latch the poison. Idempotent and\n    /// safe to repeat: `CancelIoEx` on a handle with nothing pending is a\n    /// no-op, and re-cancel closes the race where a write was issued a beat\n    /// AFTER a cancel (cancellation only reaches ALREADY-issued ops).\n    /// `cause` (the organic caller's write error) rides the retirement record\n    /// when the retirement is NOT deadline-driven.\n    fn poison_and_cancel(&self, cause: Option<&io::Error>) {\n        if !self.poisoned.swap(true, Ordering::AcqRel) {\n            // First poison of this conn: loud, once. The token is class-split\n            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\n            // observable), organic fast-fail → CONN_WRITE_RETIRED.\n            eprintln!(\n                \"{}\",\n                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)\n            );\n        }\n        #[cfg(windows)]\n        // SAFETY: scalar kernel32 call on a handle whose owning half is kept\n        // alive until Drop (gate / writer stack / graveyard — see `raw`).\n        unsafe {\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\n        }\n        #[cfg(unix)]\n        // SAFETY: scalar shutdown(2) on an fd alive until Drop. SHUT_RDWR\n        // fails the blocked write (EPIPE), EOFs the parked read, and fails\n        // every future op — on Unix this one call IS the whole teardown.\n        unsafe {\n            let _ = libc::shutdown(self.raw, libc::SHUT_RDWR);\n        }\n        // Wake gate-waiters so they observe the poison instead of sleeping\n        // out their full deadline, and the watchdog so it re-evaluates.\n        self.gate_cv.notify_all();\n        self.dog_cv.notify_all();\n    }\n\n    /// Sever the physical connection (exactly once), AFTER the canceled op\n    /// reported completion: on Windows `DisconnectNamedPipe` forces the\n    /// client off (its next access fails), makes a re-issued server-side\n    /// read fail (so `handle_conn` reaches EOF cleanup even if it slipped a\n    /// new read in after the cancel), and defuses the drop-limbo flush; the\n    /// trailing re-cancel covers exactly that slipped-in read. On Unix the\n    /// `shutdown` in [`poison_and_cancel`] already did all of this.\n    // On Unix the cfg(windows) teardown strips out and the idempotence\n    // guard's early `return` becomes the function tail — needless_return\n    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\n    // would leave Unix an empty if-body (`clippy::needless_if`) instead.\n    #[cfg_attr(unix, allow(clippy::needless_return))]\n    fn sever(&self) {\n        if self.severed.swap(true, Ordering::AcqRel) {\n            return;\n        }\n        #[cfg(windows)]\n        // SAFETY: as in poison_and_cancel — handle outlives the watchdog.\n        unsafe {\n            let _ = win::DisconnectNamedPipe(self.raw);\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\n        }\n    }\n\n    /// The watchdog body: park until a write is in flight, then sleep to its\n    /// absolute deadline. If the op is still in flight at the deadline,\n    /// poison + cancel, re-canceling periodically until the op reports\n    /// completion (the writer clears `inflight` the instant `write_frame`\n    /// returns — that return IS the buffer-release handshake), then sever.\n    fn watchdog(&self) {\n        let mut d = recover(&self.dog);\n        loop {\n            // Park until armed (or shutdown).\n            let (op, deadline) = loop {\n                if d.shutdown {\n                    return;\n                }\n                if let Some(armed) = d.inflight {\n                    break armed;\n                }\n                d = self\n                    .dog_cv\n                    .wait(d)\n                    .unwrap_or_else(|p| p.into_inner());\n            };\n            // Sleep toward the deadline while THIS op stays in flight.\n            let fired = loop {\n                if d.shutdown {\n                    return;\n                }\n                match d.inflight {\n                    Some((cur, _)) if cur == op => {}\n                    // Completed (or a newer op armed) before the deadline.\n                    _ => break false,\n                }\n                let now = Instant::now();\n                if now >= deadline {\n                    break true;\n                }\n                let (g, _) = self\n                    .dog_cv\n                    .wait_timeout(d, deadline - now)\n                    .unwrap_or_else(|p| p.into_inner());\n                d = g;\n            };\n            if !fired {\n                continue;\n            }\n            // Deadline: abort out of band, then wait for the canceled op to\n            // report completion before severing (re-cancel each slice — a\n            // cancel that raced the WriteFileEx issuance reaches nothing, so\n            // one shot is not enough). Latch timed_out FIRST so the aborted\n            // writer observes its failure as a DEADLINE retirement (wedge),\n            // not an organic conn death.\n            drop(d);\n            self.timed_out.store(true, Ordering::Release);\n            self.poison_and_cancel(None);\n            d = recover(&self.dog);\n            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\n                let (g, _) = self\n                    .dog_cv\n                    .wait_timeout(d, Duration::from_millis(100))\n                    .unwrap_or_else(|p| p.into_inner());\n                d = g;\n                drop(d);\n                self.poison_and_cancel(None);\n                d = recover(&self.dog);\n            }\n            if d.shutdown {\n                return;\n            }\n            drop(d);\n            self.sever();\n            d = recover(&self.dog);\n        }\n    }\n}\n\n/// The shared, serialized, BOUNDED send side of one physical broker\n/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\n/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\npub(crate) struct BrokerConn {\n    inner: Arc<Inner>,\n    /// Joined on drop — after `shutdown` is raised — so the raw handle the\n    /// watchdog aborts through strictly outlives every raw use.\n    dog: Option<JoinHandle<()>>,\n}\n\nimpl BrokerConn {\n    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\n    /// deadline (gate-wait + OS write completion) — the broker passes its\n    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\n    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\n        let raw = raw_of(&half);\n        let inner = Arc::new(Inner {\n            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\n            label: Mutex::new(String::new()),\n            first_write_logged: AtomicBool::new(false),\n            gate: Mutex::new(Gate { half: Some(half) }),\n            gate_cv: Condvar::new(),\n            dog: Mutex::new(Dog {\n                inflight: None,\n                shutdown: false,\n            }),\n            dog_cv: Condvar::new(),\n            poisoned: AtomicBool::new(false),\n            timed_out: AtomicBool::new(false),\n            severed: AtomicBool::new(false),\n            graveyard: Mutex::new(None),\n            op_seq: AtomicU64::new(0),\n            bound,\n            raw,\n        });\n        let dog_inner = Arc::clone(&inner);\n        let dog = thread::Builder::new()\n            .name(\"conn-watchdog\".into())\n            .spawn(move || dog_inner.watchdog())\n            .ok();\n        BrokerConn { inner, dog }\n    }\n\n    /// The stable per-physical-conn id — what lifecycle records key on\n    /// (`Arc::ptr_eq` identity does not survive a log line).\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    pub(crate) fn id(&self) -> u64 {\n        self.inner.id\n    }\n\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\n    /// endpoint/session where known — e.g. `role=brain`,\n…\n}\n\n[Showing lines 179-463 of 815. Use :464 to continue]"}],"details":{"truncation":{"content":"    raw: isize,\r\n    #[cfg(unix)]\r\n    raw: i32,\r\n}\r\n\r\n/// Recover a mutex guard even if a holder panicked — the state these guards\r\n/// protect is a plain slot/flag pair, valid on every panic boundary.\r\nfn recover<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {\r\n    match m.lock() {\r\n        Ok(g) => g,\r\n        Err(p) => p.into_inner(),\r\n    }\r\n}\r\n\r\n#[cfg(windows)]\r\nfn raw_of(half: &SendHalf) -> isize {\r\n    use std::os::windows::io::{AsHandle, AsRawHandle};\r\n    match half {\r\n        SendHalf::NamedPipe(h) => h.as_handle().as_raw_handle() as isize,\r\n    }\r\n}\r\n\r\n#[cfg(unix)]\r\nfn raw_of(half: &SendHalf) -> i32 {\r\n    use std::os::fd::{AsFd, AsRawFd};\r\n    match half {\r\n        SendHalf::UdSocket(h) => h.as_fd().as_raw_fd(),\r\n    }\r\n}\r\n\r\n#[cfg(windows)]\r\nmod win {\r\n    // kernel32 is in the MSVC default link set — raw externs, no new dep\r\n    // (the deelevate.rs / daemon.rs posture).\r\n    extern \"system\" {\r\n        pub fn CancelIoEx(handle: isize, overlapped: *mut core::ffi::c_void) -> i32;\r\n        pub fn DisconnectNamedPipe(handle: isize) -> i32;\r\n    }\r\n}\r\n\r\nimpl Inner {\r\n    /// The attribution snapshot every lifecycle record carries: stable conn id,\r\n    /// accreted role/endpoint/session facts, wall + monotonic stamps.\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    fn attribution(&self) -> String {\r\n        let label = recover(&self.label);\r\n        let facts: &str = if label.is_empty() { \"role=unattributed\" } else { &label };\r\n        format!(\"conn={} {} {}\", self.id, facts, log_stamp())\r\n    }\r\n\r\n    /// Render the once-per-conn retirement record — the F-039 token split\r\n    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\r\n    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\r\n    /// watches); an ORGANIC write failure (peer death / broken pipe on an\r\n    /// already-dead counterpart, the offline-peer dial churn) emits the\r\n    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\r\n    /// SAME attribution fields — quieter class, never thinner (the retired\r\n    /// class is what discriminates fresh-carrier churn from interleave\r\n    /// artifact in the field). Behavior is identical either way: poison,\r\n    /// abort, retire, never reuse.\r\n    // [impl->REQ-CONN-POISON-DIAL-SCOPE]\r\n    fn render_retirement(&self, timed_out: bool, cause: Option<&io::Error>) -> String {\r\n        if timed_out {\r\n            format!(\r\n                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\r\n                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\r\n                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\r\n                self.attribution(),\r\n                self.bound.as_millis()\r\n            )\r\n        } else {\r\n            let cause = match cause {\r\n                Some(e) => format!(\"{:?}: {e}\", e.kind()),\r\n                None => \"write failed\".to_string(),\r\n            };\r\n            format!(\r\n                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\r\n                 broker conn poisoned + retired, never reused (same retire behavior \\\r\n                 as POISONED — only the failure class differs) \\\r\n                 [REQ-CONN-POISON-DIAL-SCOPE]\",\r\n                self.attribution()\r\n            )\r\n        }\r\n    }\r\n\r\n    /// One BOUNDED per-conn lifecycle record (leg d, doyle-confirmed\r\n    /// UNCONDITIONAL): `write-start` (first write only), `transport-close`\r\n    /// (drop), `writer-exit` / `*-attach` / `*-replaced` (emitted by the\r\n    /// broker's sink machinery through [`BrokerConn::lifecycle_event`]). The\r\n    /// timeout/cancel leg is the retirement record itself.\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    fn lifecycle(&self, event: &str, extra: &str) {\r\n        let sep = if extra.is_empty() { \"\" } else { \" \" };\r\n        eprintln!(\r\n            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\r\n            self.attribution()\r\n        );\r\n    }\r\n\r\n    /// Abort the connection's in-flight I/O OUT OF BAND (both directions —\r\n    /// the halves share one OS object) and latch the poison. Idempotent and\r\n    /// safe to repeat: `CancelIoEx` on a handle with nothing pending is a\r\n    /// no-op, and re-cancel closes the race where a write was issued a beat\r\n    /// AFTER a cancel (cancellation only reaches ALREADY-issued ops).\r\n    /// `cause` (the organic caller's write error) rides the retirement record\r\n    /// when the retirement is NOT deadline-driven.\r\n    fn poison_and_cancel(&self, cause: Option<&io::Error>) {\r\n        if !self.poisoned.swap(true, Ordering::AcqRel) {\r\n            // First poison of this conn: loud, once. The token is class-split\r\n            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\r\n            // observable), organic fast-fail → CONN_WRITE_RETIRED.\r\n            eprintln!(\r\n                \"{}\",\r\n                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)\r\n            );\r\n        }\r\n        #[cfg(windows)]\r\n        // SAFETY: scalar kernel32 call on a handle whose owning half is kept\r\n        // alive until Drop (gate / writer stack / graveyard — see `raw`).\r\n        unsafe {\r\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\r\n        }\r\n        #[cfg(unix)]\r\n        // SAFETY: scalar shutdown(2) on an fd alive until Drop. SHUT_RDWR\r\n        // fails the blocked write (EPIPE), EOFs the parked read, and fails\r\n        // every future op — on Unix this one call IS the whole teardown.\r\n        unsafe {\r\n            let _ = libc::shutdown(self.raw, libc::SHUT_RDWR);\r\n        }\r\n        // Wake gate-waiters so they observe the poison instead of sleeping\r\n        // out their full deadline, and the watchdog so it re-evaluates.\r\n        self.gate_cv.notify_all();\r\n        self.dog_cv.notify_all();\r\n    }\r\n\r\n    /// Sever the physical connection (exactly once), AFTER the canceled op\r\n    /// reported completion: on Windows `DisconnectNamedPipe` forces the\r\n    /// client off (its next access fails), makes a re-issued server-side\r\n    /// read fail (so `handle_conn` reaches EOF cleanup even if it slipped a\r\n    /// new read in after the cancel), and defuses the drop-limbo flush; the\r\n    /// trailing re-cancel covers exactly that slipped-in read. On Unix the\r\n    /// `shutdown` in [`poison_and_cancel`] already did all of this.\r\n    // On Unix the cfg(windows) teardown strips out and the idempotence\r\n    // guard's early `return` becomes the function tail — needless_return\r\n    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\r\n    // would leave Unix an empty if-body (`clippy::needless_if`) instead.\r\n    #[cfg_attr(unix, allow(clippy::needless_return))]\r\n    fn sever(&self) {\r\n        if self.severed.swap(true, Ordering::AcqRel) {\r\n            return;\r\n        }\r\n        #[cfg(windows)]\r\n        // SAFETY: as in poison_and_cancel — handle outlives the watchdog.\r\n        unsafe {\r\n            let _ = win::DisconnectNamedPipe(self.raw);\r\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\r\n        }\r\n    }\r\n\r\n    /// The watchdog body: park until a write is in flight, then sleep to its\r\n    /// absolute deadline. If the op is still in flight at the deadline,\r\n    /// poison + cancel, re-canceling periodically until the op reports\r\n    /// completion (the writer clears `inflight` the instant `write_frame`\r\n    /// returns — that return IS the buffer-release handshake), then sever.\r\n    fn watchdog(&self) {\r\n        let mut d = recover(&self.dog);\r\n        loop {\r\n            // Park until armed (or shutdown).\r\n            let (op, deadline) = loop {\r\n                if d.shutdown {\r\n                    return;\r\n                }\r\n                if let Some(armed) = d.inflight {\r\n                    break armed;\r\n                }\r\n                d = self\r\n                    .dog_cv\r\n                    .wait(d)\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n            };\r\n            // Sleep toward the deadline while THIS op stays in flight.\r\n            let fired = loop {\r\n                if d.shutdown {\r\n                    return;\r\n                }\r\n                match d.inflight {\r\n                    Some((cur, _)) if cur == op => {}\r\n                    // Completed (or a newer op armed) before the deadline.\r\n                    _ => break false,\r\n                }\r\n                let now = Instant::now();\r\n                if now >= deadline {\r\n                    break true;\r\n                }\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, deadline - now)\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n            };\r\n            if !fired {\r\n                continue;\r\n            }\r\n            // Deadline: abort out of band, then wait for the canceled op to\r\n            // report completion before severing (re-cancel each slice — a\r\n            // cancel that raced the WriteFileEx issuance reaches nothing, so\r\n            // one shot is not enough). Latch timed_out FIRST so the aborted\r\n            // writer observes its failure as a DEADLINE retirement (wedge),\r\n            // not an organic conn death.\r\n            drop(d);\r\n            self.timed_out.store(true, Ordering::Release);\r\n            self.poison_and_cancel(None);\r\n            d = recover(&self.dog);\r\n            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, Duration::from_millis(100))\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n                drop(d);\r\n                self.poison_and_cancel(None);\r\n                d = recover(&self.dog);\r\n            }\r\n            if d.shutdown {\r\n                return;\r\n            }\r\n            drop(d);\r\n            self.sever();\r\n            d = recover(&self.dog);\r\n        }\r\n    }\r\n}\r\n\r\n/// The shared, serialized, BOUNDED send side of one physical broker\r\n/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\r\n/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\r\npub(crate) struct BrokerConn {\r\n    inner: Arc<Inner>,\r\n    /// Joined on drop — after `shutdown` is raised — so the raw handle the\r\n    /// watchdog aborts through strictly outlives every raw use.\r\n    dog: Option<JoinHandle<()>>,\r\n}\r\n\r\nimpl BrokerConn {\r\n    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\r\n    /// deadline (gate-wait + OS write completion) — the broker passes its\r\n    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\r\n    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\r\n        let raw = raw_of(&half);\r\n        let inner = Arc::new(Inner {\r\n            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\r\n            label: Mutex::new(String::new()),\r\n            first_write_logged: AtomicBool::new(false),\r\n            gate: Mutex::new(Gate { half: Some(half) }),\r\n            gate_cv: Condvar::new(),\r\n            dog: Mutex::new(Dog {\r\n                inflight: None,\r\n                shutdown: false,\r\n            }),\r\n            dog_cv: Condvar::new(),\r\n            poisoned: AtomicBool::new(false),\r\n            timed_out: AtomicBool::new(false),\r\n            severed: AtomicBool::new(false),\r\n            graveyard: Mutex::new(None),\r\n            op_seq: AtomicU64::new(0),\r\n            bound,\r\n            raw,\r\n        });\r\n        let dog_inner = Arc::clone(&inner);\r\n        let dog = thread::Builder::new()\r\n            .name(\"conn-watchdog\".into())\r\n            .spawn(move || dog_inner.watchdog())\r\n            .ok();\r\n        BrokerConn { inner, dog }\r\n    }\r\n\r\n    /// The stable per-physical-conn id — what lifecycle records key on\r\n    /// (`Arc::ptr_eq` identity does not survive a log line).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn id(&self) -> u64 {\r\n        self.inner.id\r\n    }\r\n\r\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\r\n    /// endpoint/session where known — e.g. `role=brain`,\r","truncated":true,"truncatedBy":"lines","totalLines":637,"totalBytes":12273,"outputLines":285,"outputBytes":12273,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"struct Inner {\n…\n    raw: isize,\r\n    #[cfg(unix)]\r\n    raw: i32,\r\n}\r\n\r\n/// Recover a mutex guard even if a holder panicked — the state these guards\r\n/// protect is a plain slot/flag pair, valid on every panic boundary.\r\nfn recover<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {\r\n    match m.lock() {\r\n        Ok(g) => g,\r\n        Err(p) => p.into_inner(),\r\n    }\r\n}\r\n\r\n#[cfg(windows)]\r\nfn raw_of(half: &SendHalf) -> isize {\r\n    use std::os::windows::io::{AsHandle, AsRawHandle};\r\n    match half {\r\n        SendHalf::NamedPipe(h) => h.as_handle().as_raw_handle() as isize,\r\n    }\r\n}\r\n\r\n#[cfg(unix)]\r\nfn raw_of(half: &SendHalf) -> i32 {\r\n    use std::os::fd::{AsFd, AsRawFd};\r\n    match half {\r\n        SendHalf::UdSocket(h) => h.as_fd().as_raw_fd(),\r\n    }\r\n}\r\n\r\n#[cfg(windows)]\r\nmod win {\r\n    // kernel32 is in the MSVC default link set — raw externs, no new dep\r\n    // (the deelevate.rs / daemon.rs posture).\r\n    extern \"system\" {\r\n        pub fn CancelIoEx(handle: isize, overlapped: *mut core::ffi::c_void) -> i32;\r\n        pub fn DisconnectNamedPipe(handle: isize) -> i32;\r\n    }\r\n}\r\n\r\nimpl Inner {\r\n    /// The attribution snapshot every lifecycle record carries: stable conn id,\r\n    /// accreted role/endpoint/session facts, wall + monotonic stamps.\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    fn attribution(&self) -> String {\r\n        let label = recover(&self.label);\r\n        let facts: &str = if label.is_empty() { \"role=unattributed\" } else { &label };\r\n        format!(\"conn={} {} {}\", self.id, facts, log_stamp())\r\n    }\r\n\r\n    /// Render the once-per-conn retirement record — the F-039 token split\r\n    /// (leg a): `CONN_WRITE_POISONED` is RESERVED for the deadline class\r\n    /// (`timed_out` latched — the wedge observable blackhole-controller.ps1\r\n    /// watches); an ORGANIC write failure (peer death / broken pipe on an\r\n    /// already-dead counterpart, the offline-peer dial churn) emits the\r\n    /// distinct `CONN_WRITE_RETIRED` token carrying the error kind and the\r\n    /// SAME attribution fields — quieter class, never thinner (the retired\r\n    /// class is what discriminates fresh-carrier churn from interleave\r\n    /// artifact in the field). Behavior is identical either way: poison,\r\n    /// abort, retire, never reuse.\r\n    // [impl->REQ-CONN-POISON-DIAL-SCOPE]\r\n    fn render_retirement(&self, timed_out: bool, cause: Option<&io::Error>) -> String {\r\n        if timed_out {\r\n            format!(\r\n                \"CONN_WRITE_POISONED: {} — physical broker conn retired: a write \\\r\n                 exceeded its {}ms bound; in-flight I/O aborted, conn never reused \\\r\n                 [REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\",\r\n                self.attribution(),\r\n                self.bound.as_millis()\r\n            )\r\n        } else {\r\n            let cause = match cause {\r\n                Some(e) => format!(\"{:?}: {e}\", e.kind()),\r\n                None => \"write failed\".to_string(),\r\n            };\r\n            format!(\r\n                \"CONN_WRITE_RETIRED: {} — organic write failure ({cause}); physical \\\r\n                 broker conn poisoned + retired, never reused (same retire behavior \\\r\n                 as POISONED — only the failure class differs) \\\r\n                 [REQ-CONN-POISON-DIAL-SCOPE]\",\r\n                self.attribution()\r\n            )\r\n        }\r\n    }\r\n\r\n    /// One BOUNDED per-conn lifecycle record (leg d, doyle-confirmed\r\n    /// UNCONDITIONAL): `write-start` (first write only), `transport-close`\r\n    /// (drop), `writer-exit` / `*-attach` / `*-replaced` (emitted by the\r\n    /// broker's sink machinery through [`BrokerConn::lifecycle_event`]). The\r\n    /// timeout/cancel leg is the retirement record itself.\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    fn lifecycle(&self, event: &str, extra: &str) {\r\n        let sep = if extra.is_empty() { \"\" } else { \" \" };\r\n        eprintln!(\r\n            \"CONN_LIFECYCLE: {} event={event}{sep}{extra} [REQ-CONN-POISON-ATTRIBUTION]\",\r\n            self.attribution()\r\n        );\r\n    }\r\n\r\n    /// Abort the connection's in-flight I/O OUT OF BAND (both directions —\r\n    /// the halves share one OS object) and latch the poison. Idempotent and\r\n    /// safe to repeat: `CancelIoEx` on a handle with nothing pending is a\r\n    /// no-op, and re-cancel closes the race where a write was issued a beat\r\n    /// AFTER a cancel (cancellation only reaches ALREADY-issued ops).\r\n    /// `cause` (the organic caller's write error) rides the retirement record\r\n    /// when the retirement is NOT deadline-driven.\r\n    fn poison_and_cancel(&self, cause: Option<&io::Error>) {\r\n        if !self.poisoned.swap(true, Ordering::AcqRel) {\r\n            // First poison of this conn: loud, once. The token is class-split\r\n            // (F-039 leg a): deadline → CONN_WRITE_POISONED (the wedge\r\n            // observable), organic fast-fail → CONN_WRITE_RETIRED.\r\n            eprintln!(\r\n                \"{}\",\r\n                self.render_retirement(self.timed_out.load(Ordering::Acquire), cause)\r\n            );\r\n        }\r\n        #[cfg(windows)]\r\n        // SAFETY: scalar kernel32 call on a handle whose owning half is kept\r\n        // alive until Drop (gate / writer stack / graveyard — see `raw`).\r\n        unsafe {\r\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\r\n        }\r\n        #[cfg(unix)]\r\n        // SAFETY: scalar shutdown(2) on an fd alive until Drop. SHUT_RDWR\r\n        // fails the blocked write (EPIPE), EOFs the parked read, and fails\r\n        // every future op — on Unix this one call IS the whole teardown.\r\n        unsafe {\r\n            let _ = libc::shutdown(self.raw, libc::SHUT_RDWR);\r\n        }\r\n        // Wake gate-waiters so they observe the poison instead of sleeping\r\n        // out their full deadline, and the watchdog so it re-evaluates.\r\n        self.gate_cv.notify_all();\r\n        self.dog_cv.notify_all();\r\n    }\r\n\r\n    /// Sever the physical connection (exactly once), AFTER the canceled op\r\n    /// reported completion: on Windows `DisconnectNamedPipe` forces the\r\n    /// client off (its next access fails), makes a re-issued server-side\r\n    /// read fail (so `handle_conn` reaches EOF cleanup even if it slipped a\r\n    /// new read in after the cancel), and defuses the drop-limbo flush; the\r\n    /// trailing re-cancel covers exactly that slipped-in read. On Unix the\r\n    /// `shutdown` in [`poison_and_cancel`] already did all of this.\r\n    // On Unix the cfg(windows) teardown strips out and the idempotence\r\n    // guard's early `return` becomes the function tail — needless_return\r\n    // there, load-bearing here. Restructuring (e.g. `if !swapped { … }`)\r\n    // would leave Unix an empty if-body (`clippy::needless_if`) instead.\r\n    #[cfg_attr(unix, allow(clippy::needless_return))]\r\n    fn sever(&self) {\r\n        if self.severed.swap(true, Ordering::AcqRel) {\r\n            return;\r\n        }\r\n        #[cfg(windows)]\r\n        // SAFETY: as in poison_and_cancel — handle outlives the watchdog.\r\n        unsafe {\r\n            let _ = win::DisconnectNamedPipe(self.raw);\r\n            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\r\n        }\r\n    }\r\n\r\n    /// The watchdog body: park until a write is in flight, then sleep to its\r\n    /// absolute deadline. If the op is still in flight at the deadline,\r\n    /// poison + cancel, re-canceling periodically until the op reports\r\n    /// completion (the writer clears `inflight` the instant `write_frame`\r\n    /// returns — that return IS the buffer-release handshake), then sever.\r\n    fn watchdog(&self) {\r\n        let mut d = recover(&self.dog);\r\n        loop {\r\n            // Park until armed (or shutdown).\r\n            let (op, deadline) = loop {\r\n                if d.shutdown {\r\n                    return;\r\n                }\r\n                if let Some(armed) = d.inflight {\r\n                    break armed;\r\n                }\r\n                d = self\r\n                    .dog_cv\r\n                    .wait(d)\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n            };\r\n            // Sleep toward the deadline while THIS op stays in flight.\r\n            let fired = loop {\r\n                if d.shutdown {\r\n                    return;\r\n                }\r\n                match d.inflight {\r\n                    Some((cur, _)) if cur == op => {}\r\n                    // Completed (or a newer op armed) before the deadline.\r\n                    _ => break false,\r\n                }\r\n                let now = Instant::now();\r\n                if now >= deadline {\r\n                    break true;\r\n                }\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, deadline - now)\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n            };\r\n            if !fired {\r\n                continue;\r\n            }\r\n            // Deadline: abort out of band, then wait for the canceled op to\r\n            // report completion before severing (re-cancel each slice — a\r\n            // cancel that raced the WriteFileEx issuance reaches nothing, so\r\n            // one shot is not enough). Latch timed_out FIRST so the aborted\r\n            // writer observes its failure as a DEADLINE retirement (wedge),\r\n            // not an organic conn death.\r\n            drop(d);\r\n            self.timed_out.store(true, Ordering::Release);\r\n            self.poison_and_cancel(None);\r\n            d = recover(&self.dog);\r\n            while matches!(d.inflight, Some((cur, _)) if cur == op) && !d.shutdown {\r\n                let (g, _) = self\r\n                    .dog_cv\r\n                    .wait_timeout(d, Duration::from_millis(100))\r\n                    .unwrap_or_else(|p| p.into_inner());\r\n                d = g;\r\n                drop(d);\r\n                self.poison_and_cancel(None);\r\n                d = recover(&self.dog);\r\n            }\r\n            if d.shutdown {\r\n                return;\r\n            }\r\n            drop(d);\r\n            self.sever();\r\n            d = recover(&self.dog);\r\n        }\r\n    }\r\n}\r\n\r\n/// The shared, serialized, BOUNDED send side of one physical broker\r\n/// connection — what [`crate::broker::SharedSend`] now aliases. Owns the\r\n/// [`SendHalf`] (it never leaves this module) and the per-conn watchdog.\r\npub(crate) struct BrokerConn {\r\n    inner: Arc<Inner>,\r\n    /// Joined on drop — after `shutdown` is raised — so the raw handle the\r\n    /// watchdog aborts through strictly outlives every raw use.\r\n    dog: Option<JoinHandle<()>>,\r\n}\r\n\r\nimpl BrokerConn {\r\n    /// Wrap a freshly-split send half. `bound` is the ABSOLUTE per-write\r\n    /// deadline (gate-wait + OS write completion) — the broker passes its\r\n    /// `brain_write_deadline()` (`SPT_BRAIN_WRITE_DEADLINE_MS` knob).\r\n    pub(crate) fn new(half: SendHalf, bound: Duration) -> Self {\r\n        let raw = raw_of(&half);\r\n        let inner = Arc::new(Inner {\r\n            id: CONN_ID_SEQ.fetch_add(1, Ordering::Relaxed) + 1,\r\n            label: Mutex::new(String::new()),\r\n            first_write_logged: AtomicBool::new(false),\r\n            gate: Mutex::new(Gate { half: Some(half) }),\r\n            gate_cv: Condvar::new(),\r\n            dog: Mutex::new(Dog {\r\n                inflight: None,\r\n                shutdown: false,\r\n            }),\r\n            dog_cv: Condvar::new(),\r\n            poisoned: AtomicBool::new(false),\r\n            timed_out: AtomicBool::new(false),\r\n            severed: AtomicBool::new(false),\r\n            graveyard: Mutex::new(None),\r\n            op_seq: AtomicU64::new(0),\r\n            bound,\r\n            raw,\r\n        });\r\n        let dog_inner = Arc::clone(&inner);\r\n        let dog = thread::Builder::new()\r\n            .name(\"conn-watchdog\".into())\r\n            .spawn(move || dog_inner.watchdog())\r\n            .ok();\r\n        BrokerConn { inner, dog }\r\n    }\r\n\r\n    /// The stable per-physical-conn id — what lifecycle records key on\r\n    /// (`Arc::ptr_eq` identity does not survive a log line).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn id(&self) -> u64 {\r\n        self.inner.id\r\n    }\r\n\r\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\r\n    /// endpoint/session where known — e.g. `role=brain`,\r\n…\n}","startLine":141,"lineNumbers":[141,null,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,null,602]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\conn.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":815,"totalBytes":12273,"outputLines":285,"outputBytes":12273,"shownRange":{"start":179,"end":463},"nextOffset":464}}},"isError":false,"timestamp":1784247534619}}
{"type":"message","id":"dbabe7c7","parentId":"b3ff0d11","timestamp":"2026-07-17T00:18:54.620Z","message":{"role":"toolResult","toolCallId":"call_pdNuZGF5vON8mrbSakgIN6Y0|fc_0ceecce325b7fe2d016a5974ee3584819885d1d761e050300b","toolName":"read","content":[{"type":"text","text":"/// stream).\npub fn run_dispatch_loop(\n    broker_name: &str,\n    paths: &DispatchPaths,\n    registry: Arc<RegistryHost>,\n    stop: &AtomicBool,\n    poll: Duration,\n) -> io::Result<()> {\n    let mut brain = connect(broker_name)?;\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n    // abandoned a stream on the first transient failure.\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n\n    while !stop.load(Ordering::Relaxed) {\n        let reply = brain.net_streams()?;\n        for s in reply.streams {\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\n            // invisible until its first write anyway — gotcha #6 — but the\n            // next_seq guard also skips a row whose pump hasn't appended yet).\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\n            if s.initiated_locally || s.next_seq == 0 {\n                continue;\n            }\n            let attempts = {\n                let mut c = claims.lock().unwrap();\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\n                    Some(attempts) => {\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\n                        attempts\n                    }\n                    None => continue,\n                }\n            };\n            let name = broker_name.to_string();\n            let origin = s.remote_id_hex.clone();\n            let stream_id = s.stream_id;\n            let floor_seq = s.floor_seq;\n            let finished = s.finished;\n            let p = paths.clone();\n            let reg = Arc::clone(&registry);\n            let claims_done = Arc::clone(&claims);\n            thread::spawn(move || {\n                let outcome = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\n                if matches!(outcome, DispatchOutcome::Served(_)) {\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\n                    // this stream's exchange is over — take its row out of\n                    // every FUTURE dispatcher generation's enumeration (the\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\n                    // an older broker without the verb answers error (KH-2.3)\n                    // and the row retires when the broker cycles.\n                    if let Ok(mut retire_brain) = connect(&name) {\n                        let _ = retire_brain.net_stream_retire(stream_id);\n                    }\n                } else {\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\n                }\n                let mut c = claims_done.lock().unwrap();\n                c.insert(\n                    stream_id,\n                    outcome_transition(&outcome, attempts, Instant::now()),\n                );\n            });\n        }\n        thread::sleep(poll);\n    }\n    Ok(())\n}\n\n/// Connect a fresh brain IPC client (retrying through broker startup).\nfn connect(broker_name: &str) -> io::Result<Brain> {\n    let mut last = io::Error::other(\"broker never answered\");\n    for _ in 0..300 {\n        match Brain::cold_start(broker_name, now_ms()) {\n            Ok(b) => return Ok(b),\n            Err(e) => last = e,\n        }\n        thread::sleep(Duration::from_millis(10));\n    }\n    Err(last)\n}\n\n[Showing lines 298-378 of 1034. Use :379 to continue]"}],"details":{"truncation":{"content":"/// stream).\r\npub fn run_dispatch_loop(\r\n    broker_name: &str,\r\n    paths: &DispatchPaths,\r\n    registry: Arc<RegistryHost>,\r\n    stop: &AtomicBool,\r\n    poll: Duration,\r\n) -> io::Result<()> {\r\n    let mut brain = connect(broker_name)?;\r\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\r\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\r\n    // abandoned a stream on the first transient failure.\r\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\r\n\r\n    while !stop.load(Ordering::Relaxed) {\r\n        let reply = brain.net_streams()?;\r\n        for s in reply.streams {\r\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\r\n            // invisible until its first write anyway — gotcha #6 — but the\r\n            // next_seq guard also skips a row whose pump hasn't appended yet).\r\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\r\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\r\n            if s.initiated_locally || s.next_seq == 0 {\r\n                continue;\r\n            }\r\n            let attempts = {\r\n                let mut c = claims.lock().unwrap();\r\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\r\n                    Some(attempts) => {\r\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\r\n                        attempts\r\n                    }\r\n                    None => continue,\r\n                }\r\n            };\r\n            let name = broker_name.to_string();\r\n            let origin = s.remote_id_hex.clone();\r\n            let stream_id = s.stream_id;\r\n            let floor_seq = s.floor_seq;\r\n            let finished = s.finished;\r\n            let p = paths.clone();\r\n            let reg = Arc::clone(&registry);\r\n            let claims_done = Arc::clone(&claims);\r\n            thread::spawn(move || {\r\n                let outcome = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\r\n                if matches!(outcome, DispatchOutcome::Served(_)) {\r\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\r\n                    // this stream's exchange is over — take its row out of\r\n                    // every FUTURE dispatcher generation's enumeration (the\r\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\r\n                    // an older broker without the verb answers error (KH-2.3)\r\n                    // and the row retires when the broker cycles.\r\n                    if let Ok(mut retire_brain) = connect(&name) {\r\n                        let _ = retire_brain.net_stream_retire(stream_id);\r\n                    }\r\n                } else {\r\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\r\n                }\r\n                let mut c = claims_done.lock().unwrap();\r\n                c.insert(\r\n                    stream_id,\r\n                    outcome_transition(&outcome, attempts, Instant::now()),\r\n                );\r\n            });\r\n        }\r\n        thread::sleep(poll);\r\n    }\r\n    Ok(())\r\n}\r\n\r\n/// Connect a fresh brain IPC client (retrying through broker startup).\r\nfn connect(broker_name: &str) -> io::Result<Brain> {\r\n    let mut last = io::Error::other(\"broker never answered\");\r\n    for _ in 0..300 {\r\n        match Brain::cold_start(broker_name, now_ms()) {\r\n            Ok(b) => return Ok(b),\r\n            Err(e) => last = e,\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    Err(last)\r","truncated":true,"truncatedBy":"lines","totalLines":737,"totalBytes":3585,"outputLines":81,"outputBytes":3585,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"/// stream).\r\npub fn run_dispatch_loop(\r\n    broker_name: &str,\r\n    paths: &DispatchPaths,\r\n    registry: Arc<RegistryHost>,\r\n    stop: &AtomicBool,\r\n    poll: Duration,\r\n) -> io::Result<()> {\r\n    let mut brain = connect(broker_name)?;\r\n    // Claim lifecycle (ADR-0038 C): in-flight / terminal / retryable —\r\n    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\r\n    // abandoned a stream on the first transient failure.\r\n    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\r\n\r\n    while !stop.load(Ordering::Relaxed) {\r\n        let reply = brain.net_streams()?;\r\n        for s in reply.streams {\r\n            // Claim only peer-initiated rows with bytes (a QUIC stream is\r\n            // invisible until its first write anyway — gotcha #6 — but the\r\n            // next_seq guard also skips a row whose pump hasn't appended yet).\r\n            // Terminal rows never re-enter; a released (Retry) claim re-enters\r\n            // after its backoff (REQ-DISPATCH-CLAIM-RETRY).\r\n            if s.initiated_locally || s.next_seq == 0 {\r\n                continue;\r\n            }\r\n            let attempts = {\r\n                let mut c = claims.lock().unwrap();\r\n                match should_claim(c.get(&s.stream_id), Instant::now()) {\r\n                    Some(attempts) => {\r\n                        c.insert(s.stream_id, ClaimState::InFlight { attempts });\r\n                        attempts\r\n                    }\r\n                    None => continue,\r\n                }\r\n            };\r\n            let name = broker_name.to_string();\r\n            let origin = s.remote_id_hex.clone();\r\n            let stream_id = s.stream_id;\r\n            let floor_seq = s.floor_seq;\r\n            let finished = s.finished;\r\n            let p = paths.clone();\r\n            let reg = Arc::clone(&registry);\r\n            let claims_done = Arc::clone(&claims);\r\n            thread::spawn(move || {\r\n                let outcome = worker(&name, stream_id, &origin, floor_seq, finished, &p, &reg);\r\n                if matches!(outcome, DispatchOutcome::Served(_)) {\r\n                    // Retirement (ADR-0038 A, REQ-REDISPATCH-FINISHED-RETIRE):\r\n                    // this stream's exchange is over — take its row out of\r\n                    // every FUTURE dispatcher generation's enumeration (the\r\n                    // KNOWN-HAZARDS 7.41 control-steal class). Best-effort:\r\n                    // an older broker without the verb answers error (KH-2.3)\r\n                    // and the row retires when the broker cycles.\r\n                    if let Ok(mut retire_brain) = connect(&name) {\r\n                        let _ = retire_brain.net_stream_retire(stream_id);\r\n                    }\r\n                } else {\r\n                    eprintln!(\"DISPATCH:{stream_id}:{outcome:?}\");\r\n                }\r\n                let mut c = claims_done.lock().unwrap();\r\n                c.insert(\r\n                    stream_id,\r\n                    outcome_transition(&outcome, attempts, Instant::now()),\r\n                );\r\n            });\r\n        }\r\n        thread::sleep(poll);\r\n    }\r\n    Ok(())\r\n}\r\n\r\n/// Connect a fresh brain IPC client (retrying through broker startup).\r\nfn connect(broker_name: &str) -> io::Result<Brain> {\r\n    let mut last = io::Error::other(\"broker never answered\");\r\n    for _ in 0..300 {\r\n        match Brain::cold_start(broker_name, now_ms()) {\r\n            Ok(b) => return Ok(b),\r\n            Err(e) => last = e,\r\n        }\r\n        thread::sleep(Duration::from_millis(10));\r\n    }\r\n    Err(last)\r\n}","startLine":298,"lineNumbers":[298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1034,"totalBytes":3585,"outputLines":81,"outputBytes":3585,"shownRange":{"start":298,"end":378},"nextOffset":379}}},"isError":false,"timestamp":1784247534619}}
{"type":"message","id":"d127ad7f","parentId":"dbabe7c7","timestamp":"2026-07-17T00:18:54.626Z","message":{"role":"toolResult","toolCallId":"call_jq5TmRg8YqIWZc16muP1S42m|fc_0ceecce325b7fe2d016a5974ee358c81989642f2ffc7eb8ce4","toolName":"read","content":[{"type":"text","text":"fn first_line(\n…\n) -> io::Result<Option<serde_json::Value>> {\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\n…\n        Ok(None) => {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id}: opener not pinned yet\"\n            )))\n        }\n        // Older broker (unknown verb answers error) or the row vanished —\n        // fall through to the ring peek, the pre-ADR-0038 path.\n        Err(_) => {}\n    }\n    peek_first_line(broker_name, stream_id, floor_seq)\n}\n\n/// Peek the stream's first retained NDJSON line on a throwaway brain\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\n/// gap check.\nfn peek_first_line(\n    broker_name: &str,\n    stream_id: u64,\n    floor_seq: u64,\n) -> io::Result<Option<serde_json::Value>> {\n    let mut brain = connect(broker_name)?;\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\n    let mut buf: Vec<u8> = Vec::new();\n    loop {\n        match brain.read_event()? {\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                buf.extend_from_slice(&bytes);\n                if let Some(nl) = buf.iter().position(|&b| b == b'\\n') {\n                    return Ok(serde_json::from_slice(&buf[..nl]).ok());\n                }\n            }\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\n                // Finished without a complete line: classify what we have\n                // (a line without a trailing newline before EOF still counts).\n                return Ok(serde_json::from_slice(&buf).ok());\n            }\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\n            _ => {}\n        }\n    }\n}\n\n/// One claimed stream's worker: peek → classify → run the family's serve\n/// loop on a fresh brain connection.\n#[allow(clippy::too_many_arguments)]\nfn worker(\n    broker_name: &str,\n    stream_id: u64,\n    origin: &str,\n    floor_seq: u64,\n    finished: bool,\n    paths: &DispatchPaths,\n    registry: &RegistryHost,\n) -> DispatchOutcome {\n    if take_injected_transient() {\n        return DispatchOutcome::Failed(\n…\n        );\n    }\n…\n}\n\n[Showing lines 409-468 of 1034. Use :469 to continue]"}],"details":{"truncation":{"content":"                \"stream {stream_id}: opener not pinned yet\"\r\n            )))\r\n        }\r\n        // Older broker (unknown verb answers error) or the row vanished —\r\n        // fall through to the ring peek, the pre-ADR-0038 path.\r\n        Err(_) => {}\r\n    }\r\n    peek_first_line(broker_name, stream_id, floor_seq)\r\n}\r\n\r\n/// Peek the stream's first retained NDJSON line on a throwaway brain\r\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\r\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\r\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\r\n/// gap check.\r\nfn peek_first_line(\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    floor_seq: u64,\r\n) -> io::Result<Option<serde_json::Value>> {\r\n    let mut brain = connect(broker_name)?;\r\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\r\n    let mut buf: Vec<u8> = Vec::new();\r\n    loop {\r\n        match brain.read_event()? {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                buf.extend_from_slice(&bytes);\r\n                if let Some(nl) = buf.iter().position(|&b| b == b'\\n') {\r\n                    return Ok(serde_json::from_slice(&buf[..nl]).ok());\r\n                }\r\n            }\r\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\r\n                // Finished without a complete line: classify what we have\r\n                // (a line without a trailing newline before EOF still counts).\r\n                return Ok(serde_json::from_slice(&buf).ok());\r\n            }\r\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n            _ => {}\r\n        }\r\n    }\r\n}\r\n\r\n/// One claimed stream's worker: peek → classify → run the family's serve\r\n/// loop on a fresh brain connection.\r\n#[allow(clippy::too_many_arguments)]\r\nfn worker(\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    origin: &str,\r\n    floor_seq: u64,\r\n    finished: bool,\r\n    paths: &DispatchPaths,\r\n    registry: &RegistryHost,\r\n) -> DispatchOutcome {\r\n    if take_injected_transient() {\r\n        return DispatchOutcome::Failed(\r","truncated":true,"truncatedBy":"lines","totalLines":626,"totalBytes":2240,"outputLines":60,"outputBytes":2240,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn first_line(\n…\n) -> io::Result<Option<serde_json::Value>> {\n    match connect(broker_name).and_then(|mut b| b.net_stream_opener(stream_id)) {\n…\n        Ok(None) => {\n            return Err(io::Error::other(format!(\n                \"stream {stream_id}: opener not pinned yet\"\r\n            )))\r\n        }\r\n        // Older broker (unknown verb answers error) or the row vanished —\r\n        // fall through to the ring peek, the pre-ADR-0038 path.\r\n        Err(_) => {}\r\n    }\r\n    peek_first_line(broker_name, stream_id, floor_seq)\r\n}\r\n\r\n/// Peek the stream's first retained NDJSON line on a throwaway brain\r\n/// connection (the pre-ADR-0038 fallback — subject to ring eviction, kept\r\n/// only for the older-broker window). Subscribes from `floor_seq` (the oldest\r\n/// retained chunk) so a rolled ring replays instead of tripping the brain's\r\n/// gap check.\r\nfn peek_first_line(\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    floor_seq: u64,\r\n) -> io::Result<Option<serde_json::Value>> {\r\n    let mut brain = connect(broker_name)?;\r\n    brain.net_stream_subscribe(stream_id, floor_seq)?;\r\n    let mut buf: Vec<u8> = Vec::new();\r\n    loop {\r\n        match brain.read_event()? {\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                buf.extend_from_slice(&bytes);\r\n                if let Some(nl) = buf.iter().position(|&b| b == b'\\n') {\r\n                    return Ok(serde_json::from_slice(&buf[..nl]).ok());\r\n                }\r\n            }\r\n            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {\r\n                // Finished without a complete line: classify what we have\r\n                // (a line without a trailing newline before EOF still counts).\r\n                return Ok(serde_json::from_slice(&buf).ok());\r\n            }\r\n            BrokerEvent::Error { message } => return Err(io::Error::other(message)),\r\n            _ => {}\r\n        }\r\n    }\r\n}\r\n\r\n/// One claimed stream's worker: peek → classify → run the family's serve\r\n/// loop on a fresh brain connection.\r\n#[allow(clippy::too_many_arguments)]\r\nfn worker(\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    origin: &str,\r\n    floor_seq: u64,\r\n    finished: bool,\r\n    paths: &DispatchPaths,\r\n    registry: &RegistryHost,\r\n) -> DispatchOutcome {\r\n    if take_injected_transient() {\r\n        return DispatchOutcome::Failed(\r\n…\n        );\n    }\n…\n}","startLine":393,"lineNumbers":[393,null,397,398,null,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,null,470,471,null,658]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1034,"totalBytes":2240,"outputLines":60,"outputBytes":2240,"shownRange":{"start":409,"end":468},"nextOffset":469}}},"isError":false,"timestamp":1784247534626}}
{"type":"message","id":"ee7806ec","parentId":"d127ad7f","timestamp":"2026-07-17T00:18:54.641Z","message":{"role":"toolResult","toolCallId":"call_ec5pDJNIbQ7kptrvvLAanhkS|fc_0ceecce325b7fe2d016a5974ee3574819886cbe9327a467850","toolName":"read","content":[{"type":"text","text":"impl StreamLog {\n…\n    fn append(&mut self, bytes: &[u8]) {\n…\n        if let Some(sub) = &self.subscriber {\n            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n            // the deadline instead of parking this producer.\n            let _ = sub.write(&frame);\n        }\n    }\n\n    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).\n    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the\n    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an\n    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,\n    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so\n    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing\n    /// pending yet.\n    fn drain(&mut self) -> (Vec<u8>, bool) {\n        let mut out = Vec::new();\n        while let Some((_, chunk)) = self.ring.pop_front() {\n            out.extend_from_slice(&chunk);\n        }\n        (out, self.finished)\n    }\n\n    /// Mark the read side finished and tell the subscriber (with the final\n    /// cursor, so it can verify completeness).\n    fn finish(&mut self) {\n        self.finished = true;\n        if let Some(sub) = &self.subscriber {\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n            // the deadline instead of parking this producer.\n            let _ = sub.write(&frame);\n        }\n    }\n\n    /// Attach `sub` as the subscriber, replaying every retained chunk with\n    /// `seq >= from_seq` (then eof, if already finished) before live streaming.\n    fn attach(&mut self, sub: SharedSend, from_seq: u64) {\n        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\n        // record — this is one of the two RCA-pinned shared-carrier classes.\n        // [impl->REQ-CONN-POISON-ATTRIBUTION]\n        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\n        sub.lifecycle_event(\n            \"stream-sub-attach\",\n            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\n        );\n        self.subscriber = Some(Arc::clone(&sub));\n        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {\n            let frame = net_stream_data_envelope(self.stream_id, *seq, bytes);\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n            // the deadline instead of parking this producer.\n            let _ = sub.write(&frame);\n        }\n        if self.finished {\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\n            // the deadline instead of parking this producer.\n            let _ = sub.write(&frame);\n        }\n    }\n\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\n    /// not unsubscribe a newer brain).\n    fn detach_if(&mut self, sub: &SharedSend) {\n        if let Some(cur) = &self.subscriber {\n            if Arc::ptr_eq(cur, sub) {\n                self.subscriber = None;\n            }\n        }\n    }\n}\n\n/// A bounded, sequenced log of connection liveness events — the broker-owned\n/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline\n/// applied to the conn table itself. `register_conn` appends `connected`; each\n/// connection's closed-watcher appends `disconnected` after removing the row.\n/// A brain subscribes with a cursor and replays events produced while it was\n/// down, so presence survives a brain restart exactly like stream reads do.\n// [impl->REQ-EP-4]\nstruct PresenceLog {\n    next_seq: u64,\n    ring: VecDeque<NetPresenceEvent>,\n…\n}\n\n[Showing lines 284-368 of 2212. Use :369 to continue]"}],"details":{"truncation":{"content":"            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\r\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\r\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\r\n            // the deadline instead of parking this producer.\r\n            let _ = sub.write(&frame);\r\n        }\r\n    }\r\n\r\n    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).\r\n    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the\r\n    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an\r\n    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,\r\n    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so\r\n    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing\r\n    /// pending yet.\r\n    fn drain(&mut self) -> (Vec<u8>, bool) {\r\n        let mut out = Vec::new();\r\n        while let Some((_, chunk)) = self.ring.pop_front() {\r\n            out.extend_from_slice(&chunk);\r\n        }\r\n        (out, self.finished)\r\n    }\r\n\r\n    /// Mark the read side finished and tell the subscriber (with the final\r\n    /// cursor, so it can verify completeness).\r\n    fn finish(&mut self) {\r\n        self.finished = true;\r\n        if let Some(sub) = &self.subscriber {\r\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\r\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\r\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\r\n            // the deadline instead of parking this producer.\r\n            let _ = sub.write(&frame);\r\n        }\r\n    }\r\n\r\n    /// Attach `sub` as the subscriber, replaying every retained chunk with\r\n    /// `seq >= from_seq` (then eof, if already finished) before live streaming.\r\n    fn attach(&mut self, sub: SharedSend, from_seq: u64) {\r\n        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\r\n        // record — this is one of the two RCA-pinned shared-carrier classes.\r\n        // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\r\n        sub.lifecycle_event(\r\n            \"stream-sub-attach\",\r\n            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\r\n        );\r\n        self.subscriber = Some(Arc::clone(&sub));\r\n        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {\r\n            let frame = net_stream_data_envelope(self.stream_id, *seq, bytes);\r\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\r\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\r\n            // the deadline instead of parking this producer.\r\n            let _ = sub.write(&frame);\r\n        }\r\n        if self.finished {\r\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\r\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\r\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\r\n            // the deadline instead of parking this producer.\r\n            let _ = sub.write(&frame);\r\n        }\r\n    }\r\n\r\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\r\n    /// not unsubscribe a newer brain).\r\n    fn detach_if(&mut self, sub: &SharedSend) {\r\n        if let Some(cur) = &self.subscriber {\r\n            if Arc::ptr_eq(cur, sub) {\r\n                self.subscriber = None;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// A bounded, sequenced log of connection liveness events — the broker-owned\r\n/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline\r\n/// applied to the conn table itself. `register_conn` appends `connected`; each\r\n/// connection's closed-watcher appends `disconnected` after removing the row.\r\n/// A brain subscribes with a cursor and replays events produced while it was\r\n/// down, so presence survives a brain restart exactly like stream reads do.\r\n// [impl->REQ-EP-4]\r\nstruct PresenceLog {\r\n    next_seq: u64,\r\n    ring: VecDeque<NetPresenceEvent>,\r","truncated":true,"truncatedBy":"lines","totalLines":1929,"totalBytes":4169,"outputLines":85,"outputBytes":4169,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl StreamLog {\n…\n    fn append(&mut self, bytes: &[u8]) {\n…\n        if let Some(sub) = &self.subscriber {\n            let frame = net_stream_data_envelope(self.stream_id, seq, bytes);\r\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\r\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\r\n            // the deadline instead of parking this producer.\r\n            let _ = sub.write(&frame);\r\n        }\r\n    }\r\n\r\n    /// Drain every undrained chunk in order (the shell-tunnel recv cursor, W3).\r\n    /// Pops the whole ring front-to-back (FIFO by `seq`) and concatenates the\r\n    /// bytes — chunk boundaries are arbitrary read-pump splits, irrelevant to an\r\n    /// opaque byte stream. Lossless + ordered: a byte leaves the ring only here,\r\n    /// once. Returns `(bytes, finished)`; `finished` reflects a clean/torn end so\r\n    /// the poller sees EOF and closes. Empty bytes with `finished=false` ⇒ nothing\r\n    /// pending yet.\r\n    fn drain(&mut self) -> (Vec<u8>, bool) {\r\n        let mut out = Vec::new();\r\n        while let Some((_, chunk)) = self.ring.pop_front() {\r\n            out.extend_from_slice(&chunk);\r\n        }\r\n        (out, self.finished)\r\n    }\r\n\r\n    /// Mark the read side finished and tell the subscriber (with the final\r\n    /// cursor, so it can verify completeness).\r\n    fn finish(&mut self) {\r\n        self.finished = true;\r\n        if let Some(sub) = &self.subscriber {\r\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\r\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\r\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\r\n            // the deadline instead of parking this producer.\r\n            let _ = sub.write(&frame);\r\n        }\r\n    }\r\n\r\n    /// Attach `sub` as the subscriber, replaying every retained chunk with\r\n    /// `seq >= from_seq` (then eof, if already finished) before live streaming.\r\n    fn attach(&mut self, sub: SharedSend, from_seq: u64) {\r\n        // Attribution (F-039 legs b+d): stream-subscriber role fact + attach\r\n        // record — this is one of the two RCA-pinned shared-carrier classes.\r\n        // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        sub.describe(&format!(\"stream-subscriber stream={}\", self.stream_id));\r\n        sub.lifecycle_event(\r\n            \"stream-sub-attach\",\r\n            &format!(\"stream={} from_seq={from_seq}\", self.stream_id),\r\n        );\r\n        self.subscriber = Some(Arc::clone(&sub));\r\n        for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {\r\n            let frame = net_stream_data_envelope(self.stream_id, *seq, bytes);\r\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\r\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\r\n            // the deadline instead of parking this producer.\r\n            let _ = sub.write(&frame);\r\n        }\r\n        if self.finished {\r\n            let frame = net_stream_eof_envelope(self.stream_id, self.next_seq);\r\n            // Bounded conn write (round-4, REQ-HAZARD-SHAREDSEND-NO-BLOCKING-\r\n            // WRITE-UNDER-LOCK): a wedged subscriber conn poisons + retires at\r\n            // the deadline instead of parking this producer.\r\n            let _ = sub.write(&frame);\r\n        }\r\n    }\r\n\r\n    /// Detach `sub` iff it is the current subscriber (a late disconnect must\r\n    /// not unsubscribe a newer brain).\r\n    fn detach_if(&mut self, sub: &SharedSend) {\r\n        if let Some(cur) = &self.subscriber {\r\n            if Arc::ptr_eq(cur, sub) {\r\n                self.subscriber = None;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// A bounded, sequenced log of connection liveness events — the broker-owned\r\n/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline\r\n/// applied to the conn table itself. `register_conn` appends `connected`; each\r\n/// connection's closed-watcher appends `disconnected` after removing the row.\r\n/// A brain subscribes with a cursor and replays events produced while it was\r\n/// down, so presence survives a brain restart exactly like stream reads do.\r\n// [impl->REQ-EP-4]\r\nstruct PresenceLog {\r\n    next_seq: u64,\r\n    ring: VecDeque<NetPresenceEvent>,\r\n…\n}","startLine":212,"lineNumbers":[212,null,251,null,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,null,371]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2212,"totalBytes":4169,"outputLines":85,"outputBytes":4169,"shownRange":{"start":284,"end":368},"nextOffset":369}}},"isError":false,"timestamp":1784247534641}}
{"type":"message","id":"ca6d65e8","parentId":"ee7806ec","timestamp":"2026-07-17T00:18:58.962Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Investigating StreamLog mutex handling**"},{"type":"toolCall","id":"call_v9WUg0I9gXUyFmfnC4iTToaB|fc_0ceecce325b7fe2d016a5974f1b07481989c26ec27fb2cb1b0","name":"grep","arguments":{"i":"Locating stream lock scope","pattern":"fn subscribe_stream|\\.attach\\(sub","path":"crates/spt-daemon/src/nethost.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":8850,"output":87,"cacheRead":46592,"cacheWrite":0,"totalTokens":55529,"reasoningTokens":20,"cost":{"input":0.044250000000000005,"output":0.00261,"cacheRead":0.023295999999999997,"cacheWrite":0,"total":0.070156}},"stopReason":"toolUse","timestamp":1784247534656,"responseId":"resp_0ceecce325b7fe2d016a5974eeef6c819894485e06c398a05e","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5974f0e1a081989bcf1d088151744d","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXTxSmcUOfsvlZCU2eBcqxeA55-RJ6FVRJwq6XaZsJg7CdQ4dYF5ND4s_PwRbHnHLLLjGjor6xHomV0BqVzE6z1ESmhDSJnR6Q5-I17ybwswUCCh0lPGLwnn8Rx1Lqe5exV52e6u2IgTlPmxotckNrhkoJr6QfJxvemD8SV5-k4VF1w74-GasYZawj0RHJyVQ3uurfIosBtgBWViJGImqpK6R46fsDPTuTpcQbxT44CJh24I1siUAvkoNujl9HHSeu4wtqpr3Jz_shUxGhVUw35jLnDchRC8PmbpHwfSxACZ6hsuvQWS0AgIHzODBUvvaUBznkeUnbCkfMQay5hMBvFSzuITP16pj0PhxJ9Jg-UYmaF5upsi4T2JrtzDSGPJ5QeQc7ShrSDD6N83iAa40vqIe1RiE5aBzOQlyhHh-GH6U9KGllhcN_WvY2LODqYqw0H8OsEEnKn79r5077Z7_WAf0-6dKIMlRV_aSLj1x3EroJd3THiHQCqhKmsZG3lJGKZy32U7xjfTtddYemM-xsOgjoQOUfk5FXF9GEj-xD2-RNcMw0I2dU6Aq4wftb73Gh7ZLiBaHQHc4nPVqIQg-harq1fjLzY46Ttu5-DMWgR7dBWGXGcbDW8zM1dP7OZ-i5zfkLHvrZwe8EshYwtR8TmsHPx2NigEdND267L2_H2ZbrwHRlb9L7gZ6HcepPeEwDnU0T4XDtixsb21GAwqtV1YxT-b_TVbRmPCtM1wunjNNnOqcg7sQ185c2eMwgVuh8KUV3s8PDVXThSdvGiLYDQhKRetlAFTVmU_j2vrTGXwPvdtetWoI3RvqWE6SjvvwhL0BI-6YpuWYRjn0Gczo9Qgkd1dC82TWgpQXe4Lllj5WabSH-vPQgkxeJc795hAFlFQWpmmxv1WJ684_rSUnJEcS5m3MpMVW4Go2stG2mQ76x8ClDnXZvaVy-BY2ek9Huqj6IyO8QorJAVTBunvAB4MwVZf3lIGevJMbdsf6spxNgAQQqaePuEqJiBoBQ_-BKjRTUd35Of0LUCChXe2uo4VIg==","summary":[{"type":"summary_text","text":"**Investigating StreamLog mutex handling**"}]},{"id":"fc_0ceecce325b7fe2d016a5974f1b07481989c26ec27fb2cb1b0","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating stream lock scope\",\"pattern\":\"fn subscribe_stream|\\\\.attach\\\\(sub\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_v9WUg0I9gXUyFmfnC4iTToaB","name":"grep"}]},"duration":4265.499399997294,"ttft":2217.7841999977827,"contextSnapshot":{"promptTokens":55442,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_v9WUg0I9gXUyFmfnC4iTToaB|fc_0ceecce325b7fe2d016a5974f1b07481989c26ec27fb2cb1b0","toolName":"grep","startedAt":"2026-07-17T00:18:58.962Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Locating stream lock scope"},"id":"9941415c","parentId":"ca6d65e8","timestamp":"2026-07-17T00:18:58.962Z"}
{"type":"message","id":"ce474bb3","parentId":"9941415c","timestamp":"2026-07-17T00:18:58.967Z","message":{"role":"toolResult","toolCallId":"call_v9WUg0I9gXUyFmfnC4iTToaB|fc_0ceecce325b7fe2d016a5974f1b07481989c26ec27fb2cb1b0","toolName":"grep","content":[{"type":"text","text":" 1675|    // [impl->REQ-DAEMON-2]\n*1676|    pub(crate) fn subscribe_stream(\n 1677|        &self,\n 1678|        stream_id: u64,\n 1679|        sub: SharedSend,\n...\n 1691|        };\n*1692|        log.lock().unwrap().attach(sub, from_seq);\n 1693|        Ok(())\n 1694|    }\n 1695|\n...\n 1711|    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\n*1712|        self.shared.presence.lock().unwrap().attach(sub, from_seq);\n 1713|    }\n 1714|\n 1715|    /// Detach `sub` from presence iff it is the current subscriber (brain"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":3,"fileCount":1,"files":["crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":3}],"truncated":false,"displayContent":" 1675│    // [impl->REQ-DAEMON-2]\n*1676│    pub(crate) fn subscribe_stream(\n 1677│        &self,\n 1678│        stream_id: u64,\n 1679│        sub: SharedSend,\n     │...\n 1691│        };\n*1692│        log.lock().unwrap().attach(sub, from_seq);\n 1693│        Ok(())\n 1694│    }\n 1695│\n     │...\n 1711│    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\n*1712│        self.shared.presence.lock().unwrap().attach(sub, from_seq);\n 1713│    }\n 1714│\n 1715│    /// Detach `sub` from presence iff it is the current subscriber (brain"},"isError":false,"timestamp":1784247538966}}
{"type":"message","id":"ae23e76a","parentId":"ce474bb3","timestamp":"2026-07-17T00:19:01.515Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_4S6fqPBnlenupJG6K0zq5MwW|fc_0ceecce325b7fe2d016a5974f469cc81988d7451f62a3504a2","name":"read","arguments":{"i":"Reading stream subscription locks","path":"crates/spt-daemon/src/nethost.rs","selector":"1650-1710"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":2978,"output":42,"cacheRead":52736,"cacheWrite":0,"totalTokens":55756,"cost":{"input":0.01489,"output":0.00126,"cacheRead":0.026368,"cacheWrite":0,"total":0.042518}},"stopReason":"toolUse","timestamp":1784247538972,"responseId":"resp_0ceecce325b7fe2d016a5974f348f8819895686ad13b551487","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_0ceecce325b7fe2d016a5974f469cc81988d7451f62a3504a2","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading stream subscription locks\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"1650-1710\"}","call_id":"call_4S6fqPBnlenupJG6K0zq5MwW","name":"read"}]},"duration":2530.5005999952555,"ttft":1432.0177999958396,"contextSnapshot":{"promptTokens":55714,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_4S6fqPBnlenupJG6K0zq5MwW|fc_0ceecce325b7fe2d016a5974f469cc81988d7451f62a3504a2","toolName":"read","startedAt":"2026-07-17T00:19:01.515Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading stream subscription locks"},"id":"6de2278c","parentId":"ae23e76a","timestamp":"2026-07-17T00:19:01.515Z"}
{"type":"message","id":"dc0aa002","parentId":"6de2278c","timestamp":"2026-07-17T00:19:01.532Z","message":{"role":"toolResult","toolCallId":"call_4S6fqPBnlenupJG6K0zq5MwW|fc_0ceecce325b7fe2d016a5974f469cc81988d7451f62a3504a2","toolName":"read","content":[{"type":"text","text":"    fn inbound_rows_on(&self, conn_id: u64) -> HashSet<u64> {\n        self.shared\n…\n            .collect()\n    }\n\n    /// Open a same-node loopback tunnel stream PAIR (M11-W3, REQ-SHELL-4) and\n    /// return `(owner_stream, shell_stream)` — the operator row the owner side\n    /// drives and the cross-wired peer row the shell side drives. The loopback\n    /// `open_stream` mints both rows in-process (one `duplex` wires them, so a\n    /// write to either send appears on the other's read log). Serialized so the\n    /// before/after diff sees exactly the one peer row this open minted.\n    // [impl->REQ-SHELL-4]\n    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {\n        let _guard = self.loopback_pair_lock.lock().unwrap();\n        let (conn_id, _) = self.dial_loopback();\n        let before = self.inbound_rows_on(conn_id);\n        let owner_stream = self.open_stream(conn_id)?;\n        let shell_stream = self\n            .inbound_rows_on(conn_id)\n            .difference(&before)\n            .copied()\n            .next()\n            .ok_or_else(|| io::Error::other(\"loopback peer row not registered\"))?;\n        Ok((owner_stream, shell_stream))\n    }\n\n    /// Attach `sub` as a stream's read subscriber, replaying from `from_seq`\n    /// (gapless resume — the PTY `subscribe` discipline on a QUIC stream).\n    // [impl->REQ-DAEMON-2]\n    pub(crate) fn subscribe_stream(\n        &self,\n        stream_id: u64,\n        sub: SharedSend,\n        from_seq: u64,\n    ) -> io::Result<()> {\n        let log = {\n            let streams = self.shared.streams.lock().unwrap();\n            let e = streams.get(&stream_id).ok_or_else(|| {\n                io::Error::new(\n                    io::ErrorKind::NotFound,\n                    format!(\"no such stream {stream_id}\"),\n                )\n            })?;\n            Arc::clone(&e.log)\n        };\n        log.lock().unwrap().attach(sub, from_seq);\n        Ok(())\n    }\n\n    /// Detach `sub` from a stream iff it is the current subscriber (brain\n    /// disconnect cleanup; the stream + its ring persist).\n    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\n        let log = {\n            let streams = self.shared.streams.lock().unwrap();\n            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\n        };\n        if let Some(log) = log {\n            log.lock().unwrap().detach_if(sub);\n        }\n    }\n\n    /// Attach `sub` as the presence subscriber, replaying liveness events from\n    /// `from_seq` (D4c) — the stream `subscribe` discipline on the conn table.\n    // [impl->REQ-EP-4]\n    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\n        self.shared.presence.lock().unwrap().attach(sub, from_seq);\n    }\n\n[Showing lines 1649-1713 of 2212. Use :1714 to continue]"}],"details":{"truncation":{"content":"            .collect()\r\n    }\r\n\r\n    /// Open a same-node loopback tunnel stream PAIR (M11-W3, REQ-SHELL-4) and\r\n    /// return `(owner_stream, shell_stream)` — the operator row the owner side\r\n    /// drives and the cross-wired peer row the shell side drives. The loopback\r\n    /// `open_stream` mints both rows in-process (one `duplex` wires them, so a\r\n    /// write to either send appears on the other's read log). Serialized so the\r\n    /// before/after diff sees exactly the one peer row this open minted.\r\n    // [impl->REQ-SHELL-4]\r\n    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {\r\n        let _guard = self.loopback_pair_lock.lock().unwrap();\r\n        let (conn_id, _) = self.dial_loopback();\r\n        let before = self.inbound_rows_on(conn_id);\r\n        let owner_stream = self.open_stream(conn_id)?;\r\n        let shell_stream = self\r\n            .inbound_rows_on(conn_id)\r\n            .difference(&before)\r\n            .copied()\r\n            .next()\r\n            .ok_or_else(|| io::Error::other(\"loopback peer row not registered\"))?;\r\n        Ok((owner_stream, shell_stream))\r\n    }\r\n\r\n    /// Attach `sub` as a stream's read subscriber, replaying from `from_seq`\r\n    /// (gapless resume — the PTY `subscribe` discipline on a QUIC stream).\r\n    // [impl->REQ-DAEMON-2]\r\n    pub(crate) fn subscribe_stream(\r\n        &self,\r\n        stream_id: u64,\r\n        sub: SharedSend,\r\n        from_seq: u64,\r\n    ) -> io::Result<()> {\r\n        let log = {\r\n            let streams = self.shared.streams.lock().unwrap();\r\n            let e = streams.get(&stream_id).ok_or_else(|| {\r\n                io::Error::new(\r\n                    io::ErrorKind::NotFound,\r\n                    format!(\"no such stream {stream_id}\"),\r\n                )\r\n            })?;\r\n            Arc::clone(&e.log)\r\n        };\r\n        log.lock().unwrap().attach(sub, from_seq);\r\n        Ok(())\r\n    }\r\n\r\n    /// Detach `sub` from a stream iff it is the current subscriber (brain\r\n    /// disconnect cleanup; the stream + its ring persist).\r\n    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\r\n        let log = {\r\n            let streams = self.shared.streams.lock().unwrap();\r\n            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\r\n        };\r\n        if let Some(log) = log {\r\n            log.lock().unwrap().detach_if(sub);\r\n        }\r\n    }\r\n\r\n    /// Attach `sub` as the presence subscriber, replaying liveness events from\r\n    /// `from_seq` (D4c) — the stream `subscribe` discipline on the conn table.\r\n    // [impl->REQ-EP-4]\r\n    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\r\n        self.shared.presence.lock().unwrap().attach(sub, from_seq);\r\n    }\r","truncated":true,"truncatedBy":"lines","totalLines":564,"totalBytes":2726,"outputLines":65,"outputBytes":2726,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    fn inbound_rows_on(&self, conn_id: u64) -> HashSet<u64> {\n        self.shared\n…\n            .collect()\r\n    }\r\n\r\n    /// Open a same-node loopback tunnel stream PAIR (M11-W3, REQ-SHELL-4) and\r\n    /// return `(owner_stream, shell_stream)` — the operator row the owner side\r\n    /// drives and the cross-wired peer row the shell side drives. The loopback\r\n    /// `open_stream` mints both rows in-process (one `duplex` wires them, so a\r\n    /// write to either send appears on the other's read log). Serialized so the\r\n    /// before/after diff sees exactly the one peer row this open minted.\r\n    // [impl->REQ-SHELL-4]\r\n    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {\r\n        let _guard = self.loopback_pair_lock.lock().unwrap();\r\n        let (conn_id, _) = self.dial_loopback();\r\n        let before = self.inbound_rows_on(conn_id);\r\n        let owner_stream = self.open_stream(conn_id)?;\r\n        let shell_stream = self\r\n            .inbound_rows_on(conn_id)\r\n            .difference(&before)\r\n            .copied()\r\n            .next()\r\n            .ok_or_else(|| io::Error::other(\"loopback peer row not registered\"))?;\r\n        Ok((owner_stream, shell_stream))\r\n    }\r\n\r\n    /// Attach `sub` as a stream's read subscriber, replaying from `from_seq`\r\n    /// (gapless resume — the PTY `subscribe` discipline on a QUIC stream).\r\n    // [impl->REQ-DAEMON-2]\r\n    pub(crate) fn subscribe_stream(\r\n        &self,\r\n        stream_id: u64,\r\n        sub: SharedSend,\r\n        from_seq: u64,\r\n    ) -> io::Result<()> {\r\n        let log = {\r\n            let streams = self.shared.streams.lock().unwrap();\r\n            let e = streams.get(&stream_id).ok_or_else(|| {\r\n                io::Error::new(\r\n                    io::ErrorKind::NotFound,\r\n                    format!(\"no such stream {stream_id}\"),\r\n                )\r\n            })?;\r\n            Arc::clone(&e.log)\r\n        };\r\n        log.lock().unwrap().attach(sub, from_seq);\r\n        Ok(())\r\n    }\r\n\r\n    /// Detach `sub` from a stream iff it is the current subscriber (brain\r\n    /// disconnect cleanup; the stream + its ring persist).\r\n    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {\r\n        let log = {\r\n            let streams = self.shared.streams.lock().unwrap();\r\n            streams.get(&stream_id).map(|e| Arc::clone(&e.log))\r\n        };\r\n        if let Some(log) = log {\r\n            log.lock().unwrap().detach_if(sub);\r\n        }\r\n    }\r\n\r\n    /// Attach `sub` as the presence subscriber, replaying liveness events from\r\n    /// `from_seq` (D4c) — the stream `subscribe` discipline on the conn table.\r\n    // [impl->REQ-EP-4]\r\n    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {\r\n        self.shared.presence.lock().unwrap().attach(sub, from_seq);\r\n    }\r","startLine":1641,"lineNumbers":[1641,1642,null,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1686,1687,1688,1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706,1707,1708,1709,1710,1711,1712,1713]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2212,"totalBytes":2726,"outputLines":65,"outputBytes":2726,"shownRange":{"start":1649,"end":1713},"nextOffset":1714}}},"isError":false,"timestamp":1784247541531}}
{"type":"message","id":"6537078b","parentId":"dc0aa002","timestamp":"2026-07-17T00:19:09.212Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Inspecting transport handle sharing**"},{"type":"toolCall","id":"call_N0YjzOIIeYNruXTuV71ouWAS|fc_0ceecce325b7fe2d016a5974fcd1cc8198be5fa1d0e2a468fd","name":"grep","arguments":{"i":"Tracing named pipe split","pattern":"enum SendHalf|type SendHalf|fn split|NamedPipe","path":"crates/spt-daemon/src/transport.rs; crates/spt-daemon/src","selector":null,"case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_T3x5BCtXDDUy5RRqQB0yQAws|fc_0ceecce325b7fe2d016a5974fcd3d481988db587a018467eb2","name":"read","arguments":{"i":"Reading shared write completion","path":"crates/spt-daemon/src/conn.rs","selector":"460-640"}},{"type":"toolCall","id":"call_Pb5wLAqwQSQXuzYpHGK965qY|fc_0ceecce325b7fe2d016a5974fcd3e081989fbcf91bd89c0f97","name":"read","arguments":{"i":"Reading broker cleanup path","path":"crates/spt-daemon/src/broker.rs","selector":"2940-3235"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3739,"output":271,"cacheRead":52736,"cacheWrite":0,"totalTokens":56746,"reasoningTokens":107,"cost":{"input":0.018695,"output":0.00813,"cacheRead":0.026368,"cacheWrite":0,"total":0.053193000000000004}},"stopReason":"toolUse","timestamp":1784247541537,"responseId":"resp_0ceecce325b7fe2d016a5974f6269481989e549daee40c7b6b","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5974f7d72881988c4de1bbb3bc380a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXT8TAXJNRbXM7l_xFta2QjW52DEUISs5u36aV-PvQeqldFkMOGgAQcZPuxlnyANTDyAibLeNg9-LrZe31-X1GH9ApIaVmt-tJ9iK7rWVUvbHi_Kl-3yVX1kQkqeybVZ8leoDfsQDbYZ59Q0jnFXa_oC15mVCJlVK_Ghra096-3Rp60MQldBgvbfrfWBzik9m09SqHE006o2IAeNOUAWLqBZSUhyo7dzIbcWHmvtuaXs5LDH_f1PEFbb-i3IXSba_I1LhMJ8hce_dlCGifmDEDi-RRy3dzmfTRlmEn29-bbnfOMVvruVZYPZY4R-1U66li9ZVBqLhdunEM-QBZYRlz6qsD4U-dBpQbDkTDfnbz6NumVbPyIJjRIX63yenTznzsLeHbHvUl9M7FUhvvA8FSrorEHU8OYEw18Ql45sTGX8mPejVmLu7idxDWk5AymGOUGiUg8eoXMQIPBBxdINhBOZZaJ_lCQ0VurVeHLaXGKlIQ6aBV297Nz7O-yjpFFJlST52hdddlBLbOgEbgqjvN_mfaapqoFWBDHHDm66dP8TarWiiKFRujzbOVscqaB-1zbU1wXnPUaWOdQQ0SbtgzApDy2tqboxbq78e5VxNBIUynkAxrxsKdA53e8FInItGsCZ3uBuQFvz8Sp1Aza5kX2unhny0zd1qablsjo8b1Q7mQ6MO4BC81DkI9SIS-lqjfhgYmK0FGmTzX-yCOyJ57yGEIzZDp2Y2gTUm7VnWglDYMgjV_WMGWDUGRSmuq2fsB5x9sOdkNrFMuVZOVrpVOFe5XO0CLwYufCrboyZiMwGK12dNBs9-2mcBWw79TjXGuoH_KmhXnW2G_MUTZlb6682LaJpSPgSmw6joaTkFvIqnE4ezKx1FsB7nxXvAzqL-lDJ9bShiM4wXVl-5EZB4uoN9ozR9_k-DLgJ6n4AL6sxxeFeuf1h69EFkhF-EvblMENZmmnZa5eraqsxxblbUg96JhPi6We-JP7F9IPqqTGwJPg3fNcgSHS19krqQjCYjNkmJ94DbC5wpBODcLmulfW7eNY6Gy-xlkALw_x8BZKqyKKUKThJ_0LcaPJRZRSTOWrqFPMdtEXBOtTqyqD36OwLwsmKL8AzOoMnajU95BZC8jZ2MpUaP5b9pJhxVi1iv3OvqZ3B67psch-Le28D33gZRMQJRdWlTs_0qoBLiJaBpXWwjCEnWfkPxEISd8FXq1MfE0_Bv5KzZUtNwbqC38owb2Vxat0OV81omna5nxRhIVhejLY5aZ-IdtPMlbisUWxLK4UeyGtq3Jxry-RmlM0qzWE18MGCR_42-zPCKbNaDc2L-wExjYUYOZLHaBziqK4lCTBKbwbDcoyJnP_2k6OstufUYAkQpD0yjrQ02B8gmRzfUGXmh2fRGWF7XiKsFhcTVjBQhko8K1g1-id87CCjdLDbti8KonKBULS49g1PIxivTSK8s0n_KWdfMd2qzcABQrAkJgiXptQhCNJ0gDbVG71sp3YcNKwLyvjwdSBXv1corQwX3WRTxWDuCuzHdZe98KJeXpAZXAWsUqICQdz_-Oxwi-gKPisS_eRxUdCBpwc1CI39wdeA2t6SfLkFcFGusw9Y75TqYujsGkCTY238-INBhvNyZ_3FxlieQqirgT6LEScxq7xgKzl96leQHIBr","summary":[{"type":"summary_text","text":"**Inspecting transport handle sharing**"}]},{"id":"fc_0ceecce325b7fe2d016a5974fcd1cc8198be5fa1d0e2a468fd","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing named pipe split\",\"pattern\":\"enum SendHalf|type SendHalf|fn split|NamedPipe\",\"path\":\"crates/spt-daemon/src/transport.rs; crates/spt-daemon/src\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_N0YjzOIIeYNruXTuV71ouWAS","name":"grep"},{"id":"fc_0ceecce325b7fe2d016a5974fcd3d481988db587a018467eb2","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading shared write completion\",\"path\":\"crates/spt-daemon/src/conn.rs\",\"selector\":\"460-640\"}","call_id":"call_T3x5BCtXDDUy5RRqQB0yQAws","name":"read"},{"id":"fc_0ceecce325b7fe2d016a5974fcd3e081989fbcf91bd89c0f97","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading broker cleanup path\",\"path\":\"crates/spt-daemon/src/broker.rs\",\"selector\":\"2940-3235\"}","call_id":"call_Pb5wLAqwQSQXuzYpHGK965qY","name":"read"}]},"duration":7672.92349999398,"ttft":2293.1233999952674,"contextSnapshot":{"promptTokens":56475,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_N0YjzOIIeYNruXTuV71ouWAS|fc_0ceecce325b7fe2d016a5974fcd1cc8198be5fa1d0e2a468fd","toolName":"grep","startedAt":"2026-07-17T00:19:09.213Z","args":{"path":"crates/spt-daemon/src/transport.rs; crates/spt-daemon/src"},"intent":"Tracing named pipe split"},"id":"0aaef893","parentId":"6537078b","timestamp":"2026-07-17T00:19:09.213Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_T3x5BCtXDDUy5RRqQB0yQAws|fc_0ceecce325b7fe2d016a5974fcd3d481988db587a018467eb2","toolName":"read","startedAt":"2026-07-17T00:19:09.213Z","args":{"path":"crates/spt-daemon/src/conn.rs"},"intent":"Reading shared write completion"},"id":"a0a0a622","parentId":"0aaef893","timestamp":"2026-07-17T00:19:09.213Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Pb5wLAqwQSQXuzYpHGK965qY|fc_0ceecce325b7fe2d016a5974fcd3e081989fbcf91bd89c0f97","toolName":"read","startedAt":"2026-07-17T00:19:09.213Z","args":{"path":"crates/spt-daemon/src/broker.rs"},"intent":"Reading broker cleanup path"},"id":"a6a6af66","parentId":"a0a0a622","timestamp":"2026-07-17T00:19:09.213Z"}
{"type":"message","id":"687e15dc","parentId":"a6a6af66","timestamp":"2026-07-17T00:19:09.221Z","message":{"role":"toolResult","toolCallId":"call_T3x5BCtXDDUy5RRqQB0yQAws|fc_0ceecce325b7fe2d016a5974fcd3d481988db587a018467eb2","toolName":"read","content":[{"type":"text","text":"impl BrokerConn {\n…\n    pub(crate) fn id(&self) -> u64 {\n        self.inner.id\n    }\n\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\n    /// endpoint/session where known — e.g. `role=brain`,\n    /// `controller session=7 by=cli@host`). Facts accumulate space-separated\n    /// and ride EVERY subsequent lifecycle record for this conn; growth is\n    /// bounded by [`LABEL_CAP`] (one `…` marker, then no-op).\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    pub(crate) fn describe(&self, fact: &str) {\n        let mut l = recover(&self.inner.label);\n        if l.ends_with('…') {\n            return;\n        }\n        if l.len() + fact.len() + 1 > LABEL_CAP {\n            l.push('…');\n            return;\n        }\n        if !l.is_empty() {\n            l.push(' ');\n        }\n        l.push_str(fact);\n    }\n\n    /// Emit a bounded per-conn lifecycle record through this conn's shared\n    /// attribution format — the broker's sink machinery uses it for\n    /// `writer-exit`, attach/replace, and detach events (leg d).\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\n    pub(crate) fn lifecycle_event(&self, event: &str, extra: &str) {\n        self.inner.lifecycle(event, extra);\n    }\n\n    /// Bounded framed write. See [`Self::write_gated`].\n    pub(crate) fn write(&self, env: &Envelope) -> io::Result<ConnWrite> {\n        self.write_gated(env, || true)\n    }\n\n    /// Bounded framed write with a pre-write gate closure, run AFTER the\n    /// serialized gate is acquired and BEFORE any byte is written — the same\n    /// serialization point the old epoch-check-under-`send.lock()` had, so a\n    /// superseded controller writer still bails atomically with respect to\n    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\n    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\n    pub(crate) fn write_gated(\n        &self,\n        env: &Envelope,\n        pre: impl FnOnce() -> bool,\n    ) -> io::Result<ConnWrite> {\n        let inner = &*self.inner;\n        let deadline = Instant::now() + inner.bound;\n\n        // ── Acquire the gate, bounded by the SAME absolute deadline. ──\n        let mut g = recover(&inner.gate);\n        loop {\n            if inner.poisoned.load(Ordering::Acquire) {\n                return Err(poisoned_err());\n            }\n            if g.half.is_some() {\n                break;\n            }\n            let now = Instant::now();\n            if now >= deadline {\n                // The write we queued behind is stuck past OUR whole bound:\n                // that op is the wedge. Abort it out of band and fail — the\n                // watchdog's own deadline (armed at that op's entry) is also\n                // in flight; both paths converge on the same idempotent\n                // poison/cancel.\n                drop(g);\n                inner.timed_out.store(true, Ordering::Release);\n                inner.poison_and_cancel(None);\n                return Err(timeout_err());\n            }\n            let (g2, _) = inner\n                .gate_cv\n                .wait_timeout(g, deadline - now)\n                .unwrap_or_else(|p| p.into_inner());\n            g = g2;\n        }\n\n        if !pre() {\n            return Ok(ConnWrite::Superseded);\n        }\n\n        // Once-per-conn `write-start` lifecycle record (leg d): marks the conn's\n        // first committed write, so a conn that later retires can be correlated\n        // to when it went active (fresh-carrier churn shows as open→start→retire\n        // triplets with fresh ids).\n        if !inner.first_write_logged.swap(true, Ordering::AcqRel) {\n            inner.lifecycle(\"write-start\", \"\");\n        }\n\n        // ── Check out the half + arm the watchdog, then write OUT of locks. ──\n        let mut half = g.half.take().expect(\"gate half present at checkout\");\n        let op = inner.op_seq.fetch_add(1, Ordering::Relaxed) + 1;\n        {\n            let mut d = recover(&inner.dog);\n            d.inflight = Some((op, deadline));\n            inner.dog_cv.notify_all();\n        }\n        drop(g);\n\n        let res = write_frame(&mut half, env);\n\n        // Completion handshake: `write_frame` has returned, so the OS op —\n        // canceled or not — reported completion and the frame buffer is\n        // released. Clear the arm BEFORE deciding health, so the watchdog\n        // never severs while the op is formally in flight.\n        {\n            let mut d = recover(&inner.dog);\n            d.inflight = None;\n            inner.dog_cv.notify_all();\n        }\n\n        let mut g = recover(&inner.gate);\n        if res.is_ok() && !inner.poisoned.load(Ordering::Acquire) {\n            g.half = Some(half);\n            drop(g);\n            inner.gate_cv.notify_all();\n            return Ok(ConnWrite::Done);\n        }\n\n        // ── Retire: poison the whole conn, abort the read side so\n        // `handle_conn` reaches EOF cleanup, sever, and park the half in the\n        // graveyard (its handle must outlive any late raw abort; never\n        // re-gated, never reused — a timed-out length-prefixed frame may be\n        // partially written). ──\n        drop(g);\n        inner.poison_and_cancel(res.as_ref().err());\n        inner.sever();\n        *recover(&inner.graveyard) = Some(half);\n        inner.gate_cv.notify_all();\n        // A DEADLINE retirement (the watchdog aborted OUR op, or the conn was\n        // already deadline-poisoned) surfaces as TimedOut — the caller can\n        // count the wedge (W2 stall-evict tally). An organic I/O failure\n        // (peer death) keeps its own error.\n        if inner.timed_out.load(Ordering::Acquire) {\n            return Err(timeout_err());\n        }\n        Err(match res {\n            Err(e) => e,\n            Ok(()) => poisoned_err(),\n        })\n    }\n}\n\n/// TEST-ONLY determinism lever: holds the serialized write gate (the half is\n/// checked out) so every writer parks AT THE GATE until the pin drops — the\n/// round-4 analog of pinning the old `Mutex<SendHalf>` across a registration\n/// window (the P1c epoch-gate keystone unit relies on it).\n#[cfg(test)]\npub(crate) struct TestGatePin {\n    inner: Arc<Inner>,\n    half: Option<SendHalf>,\n}\n\n#[cfg(test)]\nimpl Drop for TestGatePin {\n    fn drop(&mut self) {\n        let mut g = recover(&self.inner.gate);\n        g.half = self.half.take();\n        drop(g);\n        self.inner.gate_cv.notify_all();\n    }\n}\n\n#[cfg(test)]\nimpl BrokerConn {\n    /// Check the half out of the gate without arming the watchdog — writers\n    /// queue behind the gate (bounded by their own deadlines) until the\n    /// returned pin drops. Test-only; never a production path.\n    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\n        let mut g = recover(&self.inner.gate);\n        let half = g.half.take().expect(\"pin: gate half present\");\n        TestGatePin {\n            inner: Arc::clone(&self.inner),\n            half: Some(half),\n        }\n    }\n}\n\nimpl Drop for BrokerConn {\n    fn drop(&mut self) {\n        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\n        // is about to close — the terminal record of this conn id's life, with\n        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\n…\n    }\n}\n\n[Showing lines 459-643 of 815. Use :644 to continue]"}],"details":{"truncation":{"content":"        self.inner.id\r\n    }\r\n\r\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\r\n    /// endpoint/session where known — e.g. `role=brain`,\r\n    /// `controller session=7 by=cli@host`). Facts accumulate space-separated\r\n    /// and ride EVERY subsequent lifecycle record for this conn; growth is\r\n    /// bounded by [`LABEL_CAP`] (one `…` marker, then no-op).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn describe(&self, fact: &str) {\r\n        let mut l = recover(&self.inner.label);\r\n        if l.ends_with('…') {\r\n            return;\r\n        }\r\n        if l.len() + fact.len() + 1 > LABEL_CAP {\r\n            l.push('…');\r\n            return;\r\n        }\r\n        if !l.is_empty() {\r\n            l.push(' ');\r\n        }\r\n        l.push_str(fact);\r\n    }\r\n\r\n    /// Emit a bounded per-conn lifecycle record through this conn's shared\r\n    /// attribution format — the broker's sink machinery uses it for\r\n    /// `writer-exit`, attach/replace, and detach events (leg d).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn lifecycle_event(&self, event: &str, extra: &str) {\r\n        self.inner.lifecycle(event, extra);\r\n    }\r\n\r\n    /// Bounded framed write. See [`Self::write_gated`].\r\n    pub(crate) fn write(&self, env: &Envelope) -> io::Result<ConnWrite> {\r\n        self.write_gated(env, || true)\r\n    }\r\n\r\n    /// Bounded framed write with a pre-write gate closure, run AFTER the\r\n    /// serialized gate is acquired and BEFORE any byte is written — the same\r\n    /// serialization point the old epoch-check-under-`send.lock()` had, so a\r\n    /// superseded controller writer still bails atomically with respect to\r\n    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\r\n    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\r\n    pub(crate) fn write_gated(\r\n        &self,\r\n        env: &Envelope,\r\n        pre: impl FnOnce() -> bool,\r\n    ) -> io::Result<ConnWrite> {\r\n        let inner = &*self.inner;\r\n        let deadline = Instant::now() + inner.bound;\r\n\r\n        // ── Acquire the gate, bounded by the SAME absolute deadline. ──\r\n        let mut g = recover(&inner.gate);\r\n        loop {\r\n            if inner.poisoned.load(Ordering::Acquire) {\r\n                return Err(poisoned_err());\r\n            }\r\n            if g.half.is_some() {\r\n                break;\r\n            }\r\n            let now = Instant::now();\r\n            if now >= deadline {\r\n                // The write we queued behind is stuck past OUR whole bound:\r\n                // that op is the wedge. Abort it out of band and fail — the\r\n                // watchdog's own deadline (armed at that op's entry) is also\r\n                // in flight; both paths converge on the same idempotent\r\n                // poison/cancel.\r\n                drop(g);\r\n                inner.timed_out.store(true, Ordering::Release);\r\n                inner.poison_and_cancel(None);\r\n                return Err(timeout_err());\r\n            }\r\n            let (g2, _) = inner\r\n                .gate_cv\r\n                .wait_timeout(g, deadline - now)\r\n                .unwrap_or_else(|p| p.into_inner());\r\n            g = g2;\r\n        }\r\n\r\n        if !pre() {\r\n            return Ok(ConnWrite::Superseded);\r\n        }\r\n\r\n        // Once-per-conn `write-start` lifecycle record (leg d): marks the conn's\r\n        // first committed write, so a conn that later retires can be correlated\r\n        // to when it went active (fresh-carrier churn shows as open→start→retire\r\n        // triplets with fresh ids).\r\n        if !inner.first_write_logged.swap(true, Ordering::AcqRel) {\r\n            inner.lifecycle(\"write-start\", \"\");\r\n        }\r\n\r\n        // ── Check out the half + arm the watchdog, then write OUT of locks. ──\r\n        let mut half = g.half.take().expect(\"gate half present at checkout\");\r\n        let op = inner.op_seq.fetch_add(1, Ordering::Relaxed) + 1;\r\n        {\r\n            let mut d = recover(&inner.dog);\r\n            d.inflight = Some((op, deadline));\r\n            inner.dog_cv.notify_all();\r\n        }\r\n        drop(g);\r\n\r\n        let res = write_frame(&mut half, env);\r\n\r\n        // Completion handshake: `write_frame` has returned, so the OS op —\r\n        // canceled or not — reported completion and the frame buffer is\r\n        // released. Clear the arm BEFORE deciding health, so the watchdog\r\n        // never severs while the op is formally in flight.\r\n        {\r\n            let mut d = recover(&inner.dog);\r\n            d.inflight = None;\r\n            inner.dog_cv.notify_all();\r\n        }\r\n\r\n        let mut g = recover(&inner.gate);\r\n        if res.is_ok() && !inner.poisoned.load(Ordering::Acquire) {\r\n            g.half = Some(half);\r\n            drop(g);\r\n            inner.gate_cv.notify_all();\r\n            return Ok(ConnWrite::Done);\r\n        }\r\n\r\n        // ── Retire: poison the whole conn, abort the read side so\r\n        // `handle_conn` reaches EOF cleanup, sever, and park the half in the\r\n        // graveyard (its handle must outlive any late raw abort; never\r\n        // re-gated, never reused — a timed-out length-prefixed frame may be\r\n        // partially written). ──\r\n        drop(g);\r\n        inner.poison_and_cancel(res.as_ref().err());\r\n        inner.sever();\r\n        *recover(&inner.graveyard) = Some(half);\r\n        inner.gate_cv.notify_all();\r\n        // A DEADLINE retirement (the watchdog aborted OUR op, or the conn was\r\n        // already deadline-poisoned) surfaces as TimedOut — the caller can\r\n        // count the wedge (W2 stall-evict tally). An organic I/O failure\r\n        // (peer death) keeps its own error.\r\n        if inner.timed_out.load(Ordering::Acquire) {\r\n            return Err(timeout_err());\r\n        }\r\n        Err(match res {\r\n            Err(e) => e,\r\n            Ok(()) => poisoned_err(),\r\n        })\r\n    }\r\n}\r\n\r\n/// TEST-ONLY determinism lever: holds the serialized write gate (the half is\r\n/// checked out) so every writer parks AT THE GATE until the pin drops — the\r\n/// round-4 analog of pinning the old `Mutex<SendHalf>` across a registration\r\n/// window (the P1c epoch-gate keystone unit relies on it).\r\n#[cfg(test)]\r\npub(crate) struct TestGatePin {\r\n    inner: Arc<Inner>,\r\n    half: Option<SendHalf>,\r\n}\r\n\r\n#[cfg(test)]\r\nimpl Drop for TestGatePin {\r\n    fn drop(&mut self) {\r\n        let mut g = recover(&self.inner.gate);\r\n        g.half = self.half.take();\r\n        drop(g);\r\n        self.inner.gate_cv.notify_all();\r\n    }\r\n}\r\n\r\n#[cfg(test)]\r\nimpl BrokerConn {\r\n    /// Check the half out of the gate without arming the watchdog — writers\r\n    /// queue behind the gate (bounded by their own deadlines) until the\r\n    /// returned pin drops. Test-only; never a production path.\r\n    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\r\n        let mut g = recover(&self.inner.gate);\r\n        let half = g.half.take().expect(\"pin: gate half present\");\r\n        TestGatePin {\r\n            inner: Arc::clone(&self.inner),\r\n            half: Some(half),\r\n        }\r\n    }\r\n}\r\n\r\nimpl Drop for BrokerConn {\r\n    fn drop(&mut self) {\r\n        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\r\n        // is about to close — the terminal record of this conn id's life, with\r\n        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\r","truncated":true,"truncatedBy":"lines","totalLines":357,"totalBytes":7388,"outputLines":185,"outputBytes":7388,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"impl BrokerConn {\n…\n    pub(crate) fn id(&self) -> u64 {\n        self.inner.id\r\n    }\r\n\r\n    /// Accrete an attribution fact onto this conn's label (subscriber role,\r\n    /// endpoint/session where known — e.g. `role=brain`,\r\n    /// `controller session=7 by=cli@host`). Facts accumulate space-separated\r\n    /// and ride EVERY subsequent lifecycle record for this conn; growth is\r\n    /// bounded by [`LABEL_CAP`] (one `…` marker, then no-op).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn describe(&self, fact: &str) {\r\n        let mut l = recover(&self.inner.label);\r\n        if l.ends_with('…') {\r\n            return;\r\n        }\r\n        if l.len() + fact.len() + 1 > LABEL_CAP {\r\n            l.push('…');\r\n            return;\r\n        }\r\n        if !l.is_empty() {\r\n            l.push(' ');\r\n        }\r\n        l.push_str(fact);\r\n    }\r\n\r\n    /// Emit a bounded per-conn lifecycle record through this conn's shared\r\n    /// attribution format — the broker's sink machinery uses it for\r\n    /// `writer-exit`, attach/replace, and detach events (leg d).\r\n    // [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n    pub(crate) fn lifecycle_event(&self, event: &str, extra: &str) {\r\n        self.inner.lifecycle(event, extra);\r\n    }\r\n\r\n    /// Bounded framed write. See [`Self::write_gated`].\r\n    pub(crate) fn write(&self, env: &Envelope) -> io::Result<ConnWrite> {\r\n        self.write_gated(env, || true)\r\n    }\r\n\r\n    /// Bounded framed write with a pre-write gate closure, run AFTER the\r\n    /// serialized gate is acquired and BEFORE any byte is written — the same\r\n    /// serialization point the old epoch-check-under-`send.lock()` had, so a\r\n    /// superseded controller writer still bails atomically with respect to\r\n    /// its successor's writes (P1c, REQ-HAZARD-CONTROLLER-WRITER-REORDER).\r\n    /// `pre` returning false → `Ok(ConnWrite::Superseded)`, nothing written.\r\n    pub(crate) fn write_gated(\r\n        &self,\r\n        env: &Envelope,\r\n        pre: impl FnOnce() -> bool,\r\n    ) -> io::Result<ConnWrite> {\r\n        let inner = &*self.inner;\r\n        let deadline = Instant::now() + inner.bound;\r\n\r\n        // ── Acquire the gate, bounded by the SAME absolute deadline. ──\r\n        let mut g = recover(&inner.gate);\r\n        loop {\r\n            if inner.poisoned.load(Ordering::Acquire) {\r\n                return Err(poisoned_err());\r\n            }\r\n            if g.half.is_some() {\r\n                break;\r\n            }\r\n            let now = Instant::now();\r\n            if now >= deadline {\r\n                // The write we queued behind is stuck past OUR whole bound:\r\n                // that op is the wedge. Abort it out of band and fail — the\r\n                // watchdog's own deadline (armed at that op's entry) is also\r\n                // in flight; both paths converge on the same idempotent\r\n                // poison/cancel.\r\n                drop(g);\r\n                inner.timed_out.store(true, Ordering::Release);\r\n                inner.poison_and_cancel(None);\r\n                return Err(timeout_err());\r\n            }\r\n            let (g2, _) = inner\r\n                .gate_cv\r\n                .wait_timeout(g, deadline - now)\r\n                .unwrap_or_else(|p| p.into_inner());\r\n            g = g2;\r\n        }\r\n\r\n        if !pre() {\r\n            return Ok(ConnWrite::Superseded);\r\n        }\r\n\r\n        // Once-per-conn `write-start` lifecycle record (leg d): marks the conn's\r\n        // first committed write, so a conn that later retires can be correlated\r\n        // to when it went active (fresh-carrier churn shows as open→start→retire\r\n        // triplets with fresh ids).\r\n        if !inner.first_write_logged.swap(true, Ordering::AcqRel) {\r\n            inner.lifecycle(\"write-start\", \"\");\r\n        }\r\n\r\n        // ── Check out the half + arm the watchdog, then write OUT of locks. ──\r\n        let mut half = g.half.take().expect(\"gate half present at checkout\");\r\n        let op = inner.op_seq.fetch_add(1, Ordering::Relaxed) + 1;\r\n        {\r\n            let mut d = recover(&inner.dog);\r\n            d.inflight = Some((op, deadline));\r\n            inner.dog_cv.notify_all();\r\n        }\r\n        drop(g);\r\n\r\n        let res = write_frame(&mut half, env);\r\n\r\n        // Completion handshake: `write_frame` has returned, so the OS op —\r\n        // canceled or not — reported completion and the frame buffer is\r\n        // released. Clear the arm BEFORE deciding health, so the watchdog\r\n        // never severs while the op is formally in flight.\r\n        {\r\n            let mut d = recover(&inner.dog);\r\n            d.inflight = None;\r\n            inner.dog_cv.notify_all();\r\n        }\r\n\r\n        let mut g = recover(&inner.gate);\r\n        if res.is_ok() && !inner.poisoned.load(Ordering::Acquire) {\r\n            g.half = Some(half);\r\n            drop(g);\r\n            inner.gate_cv.notify_all();\r\n            return Ok(ConnWrite::Done);\r\n        }\r\n\r\n        // ── Retire: poison the whole conn, abort the read side so\r\n        // `handle_conn` reaches EOF cleanup, sever, and park the half in the\r\n        // graveyard (its handle must outlive any late raw abort; never\r\n        // re-gated, never reused — a timed-out length-prefixed frame may be\r\n        // partially written). ──\r\n        drop(g);\r\n        inner.poison_and_cancel(res.as_ref().err());\r\n        inner.sever();\r\n        *recover(&inner.graveyard) = Some(half);\r\n        inner.gate_cv.notify_all();\r\n        // A DEADLINE retirement (the watchdog aborted OUR op, or the conn was\r\n        // already deadline-poisoned) surfaces as TimedOut — the caller can\r\n        // count the wedge (W2 stall-evict tally). An organic I/O failure\r\n        // (peer death) keeps its own error.\r\n        if inner.timed_out.load(Ordering::Acquire) {\r\n            return Err(timeout_err());\r\n        }\r\n        Err(match res {\r\n            Err(e) => e,\r\n            Ok(()) => poisoned_err(),\r\n        })\r\n    }\r\n}\r\n\r\n/// TEST-ONLY determinism lever: holds the serialized write gate (the half is\r\n/// checked out) so every writer parks AT THE GATE until the pin drops — the\r\n/// round-4 analog of pinning the old `Mutex<SendHalf>` across a registration\r\n/// window (the P1c epoch-gate keystone unit relies on it).\r\n#[cfg(test)]\r\npub(crate) struct TestGatePin {\r\n    inner: Arc<Inner>,\r\n    half: Option<SendHalf>,\r\n}\r\n\r\n#[cfg(test)]\r\nimpl Drop for TestGatePin {\r\n    fn drop(&mut self) {\r\n        let mut g = recover(&self.inner.gate);\r\n        g.half = self.half.take();\r\n        drop(g);\r\n        self.inner.gate_cv.notify_all();\r\n    }\r\n}\r\n\r\n#[cfg(test)]\r\nimpl BrokerConn {\r\n    /// Check the half out of the gate without arming the watchdog — writers\r\n    /// queue behind the gate (bounded by their own deadlines) until the\r\n    /// returned pin drops. Test-only; never a production path.\r\n    pub(crate) fn pin_gate_for_test(&self) -> TestGatePin {\r\n        let mut g = recover(&self.inner.gate);\r\n        let half = g.half.take().expect(\"pin: gate half present\");\r\n        TestGatePin {\r\n            inner: Arc::clone(&self.inner),\r\n            half: Some(half),\r\n        }\r\n    }\r\n}\r\n\r\nimpl Drop for BrokerConn {\r\n    fn drop(&mut self) {\r\n        // Once-per-conn `transport-close` lifecycle record (leg d): the handle\r\n        // is about to close — the terminal record of this conn id's life, with\r\n        // how it ended (organic EOF vs poisoned vs deadline-poisoned).\r\n…\n    }\n}","startLine":422,"lineNumbers":[422,null,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549,550,551,552,553,554,555,556,557,558,559,560,561,562,563,564,565,566,567,568,569,570,571,572,573,574,575,576,577,578,579,580,581,582,583,584,585,586,587,588,589,590,591,592,593,594,595,596,597,598,599,600,601,602,603,604,605,606,607,608,609,610,611,612,613,614,615,616,617,618,619,620,621,622,623,624,625,626,627,628,629,630,631,632,633,634,635,636,637,638,639,640,641,642,643,null,662,663]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\conn.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":815,"totalBytes":7388,"outputLines":185,"outputBytes":7388,"shownRange":{"start":459,"end":643},"nextOffset":644}}},"isError":false,"timestamp":1784247549221}}
{"type":"message","id":"d03a8558","parentId":"687e15dc","timestamp":"2026-07-17T00:19:09.226Z","message":{"role":"toolResult","toolCallId":"call_N0YjzOIIeYNruXTuV71ouWAS|fc_0ceecce325b7fe2d016a5974fcd1cc8198be5fa1d0e2a468fd","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## brain.rs\n 230|    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n*231|    fn split_with_reader(stream: Stream) -> BrainConn {\n 232|        let (mut recv, send) = stream.split();\n 233|        let (tx, frames) = mpsc::channel();\n 234|        let reader = thread::Builder::new()\n## conn.rs\n 49|//! must never see a late `CancelIoEx`), and never re-gated; (5) on Windows the\n*50|//! pipe is then severed (`DisconnectNamedPipe` — the server end forces the\n 51|//! client off, so the old client's next access fails, a re-issued server read\n 52|//! fails, and the `interprocess` drop-limbo flush cannot block on unread\n 53|//! data), followed by one more `CancelIoEx` covering a read re-issued in the\n...\n 161|    timed_out: AtomicBool,\n*162|    /// The physical sever ran (exactly once; Windows `DisconnectNamedPipe`).\n 163|    severed: AtomicBool,\n 164|    /// A retired (poisoned) half parks here so its OS handle stays alive\n 165|    /// until [`BrokerConn::drop`] — after the watchdog is joined, so no raw\n*166|    /// `CancelIoEx`/`DisconnectNamedPipe` can race the `CloseHandle`.\n 167|    graveyard: Mutex<Option<SendHalf>>,\n 168|    /// Monotonic per-write op identity (the watchdog cancels a SPECIFIC op).\n 169|    op_seq: AtomicU64,\n...\n 196|    match half {\n*197|        SendHalf::NamedPipe(h) => h.as_handle().as_raw_handle() as isize,\n 198|    }\n 199|}\n 200|\n...\n 214|        pub fn CancelIoEx(handle: isize, overlapped: *mut core::ffi::c_void) -> i32;\n*215|        pub fn DisconnectNamedPipe(handle: isize) -> i32;\n 216|    }\n 217|}\n 218|\n...\n 314|    /// Sever the physical connection (exactly once), AFTER the canceled op\n*315|    /// reported completion: on Windows `DisconnectNamedPipe` forces the\n 316|    /// client off (its next access fails), makes a re-issued server-side\n 317|    /// read fail (so `handle_conn` reaches EOF cleanup even if it slipped a\n 318|    /// new read in after the cancel), and defuses the drop-limbo flush; the\n...\n 332|        unsafe {\n*333|            let _ = win::DisconnectNamedPipe(self.raw);\n 334|            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\n 335|        }\n 336|    }\n## nethost.rs\n 483|/// leaf and nowhere above it.\n*484|enum SendHalf {\n 485|    Quic(SendStream),\n 486|    Loopback(WriteHalf<DuplexStream>),\n 487|}\n## seedmap.rs\n 132|            // (a clean NotFound) instead of the busy-no-acceptor teardown window that\n*133|            // parks a Windows connect in WaitNamedPipeW. Narrows the window the bounded\n 134|            // ping (B2) also guards.\n 135|            drop(conn);\n 136|            drop(listener);\n...\n 260|/// Per-probe ceiling for the barrier's bounded [`ping_bounded`] — generous versus a\n*261|/// legitimately slow accept, finite versus the WaitNamedPipeW-forever busy-name park.\n 262|const PING_BARRIER_BOUND: std::time::Duration = std::time::Duration::from_secs(2);\n 263|\n 264|/// The [`request_stop`] barrier's BOUNDED liveness probe (REQ-HAZARD-DAEMON-STOP-BARRIER,\n...\n 266|/// named-pipe connect to a name that still EXISTS but has NO accepting instance — the\n*267|/// serve loop's teardown window — parks in `WaitNamedPipeW(NMPWAIT_WAIT_FOREVER)`, so the\n 268|/// barrier would wedge on its very first probe (while holding the `with_home` test lock —\n 269|/// the daemon-lib convoy doyle root-caused). Bound it: run the probe on a worker thread\n 270|/// and cap the wait. A genuinely gone listener still surfaces its REAL error (NotFound /\n...\n 293|/// OWN initial dial is the plain [`connect`], which — like the barrier ping before it\n*294|/// was bounded — can park FOREVER in WaitNamedPipeW when the seed name is pinned by a\n 295|/// residual server instance mid-teardown (doyle's cdb: a stop-guard re-dialing an\n 296|/// already-dying name wedged the whole convoy). Every dial on the stop path must be\n 297|/// bounded, not just the barrier ping. Same worker-thread + recv_timeout shape as"}],"details":{"scopePath":"crates/spt-daemon/src/transport.rs, crates/spt-daemon/src","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":13,"fileCount":4,"files":["crates/spt-daemon/src/brain.rs","crates/spt-daemon/src/conn.rs","crates/spt-daemon/src/nethost.rs","crates/spt-daemon/src/seedmap.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/brain.rs","count":1},{"path":"crates/spt-daemon/src/conn.rs","count":7},{"path":"crates/spt-daemon/src/nethost.rs","count":1},{"path":"crates/spt-daemon/src/seedmap.rs","count":4}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## brain.rs\n 230│    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n*231│    fn split_with_reader(stream: Stream) -> BrainConn {\n 232│        let (mut recv, send) = stream.split();\n 233│        let (tx, frames) = mpsc::channel();\n 234│        let reader = thread::Builder::new()\n## conn.rs\n  49│//! must never see a late `CancelIoEx`), and never re-gated; (5) on Windows the\n *50│//! pipe is then severed (`DisconnectNamedPipe` — the server end forces the\n  51│//! client off, so the old client's next access fails, a re-issued server read\n  52│//! fails, and the `interprocess` drop-limbo flush cannot block on unread\n  53│//! data), followed by one more `CancelIoEx` covering a read re-issued in the\n    │...\n 161│    timed_out: AtomicBool,\n*162│    /// The physical sever ran (exactly once; Windows `DisconnectNamedPipe`).\n 163│    severed: AtomicBool,\n 164│    /// A retired (poisoned) half parks here so its OS handle stays alive\n 165│    /// until [`BrokerConn::drop`] — after the watchdog is joined, so no raw\n*166│    /// `CancelIoEx`/`DisconnectNamedPipe` can race the `CloseHandle`.\n 167│    graveyard: Mutex<Option<SendHalf>>,\n 168│    /// Monotonic per-write op identity (the watchdog cancels a SPECIFIC op).\n 169│    op_seq: AtomicU64,\n    │...\n 196│    match half {\n*197│        SendHalf::NamedPipe(h) => h.as_handle().as_raw_handle() as isize,\n 198│    }\n 199│}\n 200│\n    │...\n 214│        pub fn CancelIoEx(handle: isize, overlapped: *mut core::ffi::c_void) -> i32;\n*215│        pub fn DisconnectNamedPipe(handle: isize) -> i32;\n 216│    }\n 217│}\n 218│\n    │...\n 314│    /// Sever the physical connection (exactly once), AFTER the canceled op\n*315│    /// reported completion: on Windows `DisconnectNamedPipe` forces the\n 316│    /// client off (its next access fails), makes a re-issued server-side\n 317│    /// read fail (so `handle_conn` reaches EOF cleanup even if it slipped a\n 318│    /// new read in after the cancel), and defuses the drop-limbo flush; the\n    │...\n 332│        unsafe {\n*333│            let _ = win::DisconnectNamedPipe(self.raw);\n 334│            let _ = win::CancelIoEx(self.raw, core::ptr::null_mut());\n 335│        }\n 336│    }\n## nethost.rs\n 483│/// leaf and nowhere above it.\n*484│enum SendHalf {\n 485│    Quic(SendStream),\n 486│    Loopback(WriteHalf<DuplexStream>),\n 487│}\n## seedmap.rs\n 132│            // (a clean NotFound) instead of the busy-no-acceptor teardown window that\n*133│            // parks a Windows connect in WaitNamedPipeW. Narrows the window the bounded\n 134│            // ping (B2) also guards.\n 135│            drop(conn);\n 136│            drop(listener);\n    │...\n 260│/// Per-probe ceiling for the barrier's bounded [`ping_bounded`] — generous versus a\n*261│/// legitimately slow accept, finite versus the WaitNamedPipeW-forever busy-name park.\n 262│const PING_BARRIER_BOUND: std::time::Duration = std::time::Duration::from_secs(2);\n 263│\n 264│/// The [`request_stop`] barrier's BOUNDED liveness probe (REQ-HAZARD-DAEMON-STOP-BARRIER,\n    │...\n 266│/// named-pipe connect to a name that still EXISTS but has NO accepting instance — the\n*267│/// serve loop's teardown window — parks in `WaitNamedPipeW(NMPWAIT_WAIT_FOREVER)`, so the\n 268│/// barrier would wedge on its very first probe (while holding the `with_home` test lock —\n 269│/// the daemon-lib convoy doyle root-caused). Bound it: run the probe on a worker thread\n 270│/// and cap the wait. A genuinely gone listener still surfaces its REAL error (NotFound /\n    │...\n 293│/// OWN initial dial is the plain [`connect`], which — like the barrier ping before it\n*294│/// was bounded — can park FOREVER in WaitNamedPipeW when the seed name is pinned by a\n 295│/// residual server instance mid-teardown (doyle's cdb: a stop-guard re-dialing an\n 296│/// already-dying name wedged the whole convoy). Every dial on the stop path must be\n 297│/// bounded, not just the barrier ping. Same worker-thread + recv_timeout shape as"},"isError":false,"timestamp":1784247549226}}
{"type":"message","id":"c2bef99d","parentId":"d03a8558","timestamp":"2026-07-17T00:19:09.262Z","message":{"role":"toolResult","toolCallId":"call_Pb5wLAqwQSQXuzYpHGK965qY|fc_0ceecce325b7fe2d016a5974fcd3e081989fbcf91bd89c0f97","toolName":"read","content":[{"type":"text","text":"    pub fn any_local_controller_wedged(&self) -> bool {\n…\n        let sessions = match self.sessions.lock() {\n…\n        };\n        sessions.values().any(|h| {\n            match h.log.lock() {\n                Ok(log) => log.local_controller_wedged(now, deadline),\n                // A poisoned log lock cannot be honestly read — treat as NOT wedged\n                // (fail toward promotion, never a permanent gate that strands apply).\n                Err(_) => false,\n            }\n        })\n    }\n\n    /// The count of live QUIC connections the broker-owned net endpoint holds\n    /// (D4a, ADR-0004 §B), or 0 when net-less. A direct read of the broker's own\n    /// state — the survival-assertion seam for the D7-1 process-level E2E: a QUIC\n    /// connection the broker holds must outlive a brain-PROCESS swap (restoration\n    /// D7-1), the net sibling of [`session_count`](Self::session_count).\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n    pub fn net_conn_count(&self) -> usize {\n        self.net.get().map(|h| h.conn_count() as usize).unwrap_or(0)\n    }\n\n    /// The output-log high-water (`next_seq`) of a hosted session, or `None` if\n    /// absent. It advances every time the child emits output — the\n    /// **functional**-survival seam for the D7-1 process-level E2E (doyle F1):\n    /// after a brain-PROCESS swap, writing input to the broker-held PTY child and\n    /// watching THIS advance proves the path is alive end-to-end (input → child →\n    /// echo → broker ring), not merely that the handle is held. A read-only direct\n    /// peek, like [`session_pid`](Self::session_pid).\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\n    pub fn session_output_seq(&self, id: u64) -> Option<u64> {\n        self.sessions\n            .lock()\n            .unwrap()\n            .get(&id)\n            .map(|h| recover_log(&h.log).next_seq)\n    }\n\n    /// Drive one brain connection: handshake, then dispatch its command frames\n    /// until it disconnects. On disconnect, detach this connection from any\n    /// sessions it subscribed to — the sessions themselves persist (B2: a brain\n    /// restart must not disturb the hosted children).\n    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\n        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\n        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\n        struct HandlerTally<'a>(&'a AtomicUsize);\n        impl Drop for HandlerTally<'_> {\n            fn drop(&mut self) {\n                self.0.fetch_sub(1, Ordering::AcqRel);\n            }\n        }\n        self.conn_handlers.fetch_add(1, Ordering::AcqRel);\n        let _tally = HandlerTally(&self.conn_handlers);\n\n        let (mut recv, send) = conn.split();\n        // Round-4: the send half is owned by the bounded/cancelable conn\n        // primitive from birth — every write on this physical connection is\n        // deadline-bounded, and a wedge poisons + retires the conn.\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\n        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\n        // Base attribution fact (F-039 leg b): broker clients are brains; role\n        // facts (controller/viewer/presence/stream subscriber) accrete at the\n        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\n        send.describe(\"role=brain\");\n\n        // Version handshake: this connection must be a brain (2.3).\n        recv_hello(&mut recv, Role::Brain)?;\n\n        let mut my_subs: Vec<u64> = Vec::new();\n        let mut my_stream_subs: Vec<u64> = Vec::new();\n        let mut my_presence_sub = false;\n\n        loop {\n            let env = match read_frame(&mut recv) {\n                Ok(e) => e,\n                // Any read failure (EOF included) ends the connection.\n                Err(_) => break,\n            };\n            match env.kind.as_str() {\n                KIND_SPAWN => match self.dispatch_spawn(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_SUBSCRIBE => match self.dispatch_subscribe(env, &send) {\n                    Ok(id) => my_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_UNSUBSCRIBE => {\n                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_INPUT => {\n                    if let Err(msg) = self.dispatch_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_RESIZE => {\n                    if let Err(msg) = self.dispatch_resize(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_KILL => {\n                    if let Err(msg) = self.dispatch_kill(env) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-ADAPTER-LIVE-UPDATE]\n                KIND_ADAPTER_APPLY => {\n                    if let Err(msg) = self.dispatch_adapter_apply(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STATUS => self.dispatch_net_status(&send),\n                // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\n                KIND_BROKER_IMAGE => self.dispatch_broker_image(&send),\n                KIND_STALL_EVICTS => self.dispatch_stall_evicts(&send),\n                KIND_NET_DIAL => {\n                    if let Err(msg) = self.dispatch_net_dial(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_DIAL_LOOPBACK => {\n                    if let Err(msg) = self.dispatch_net_dial_loopback(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                // [impl->REQ-PUMP-PEER-ISOLATION]\n                KIND_NET_DIAL_SUBMIT => {\n                    if let Err(msg) = self.dispatch_net_dial_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_JOIN => {\n                    if let Err(msg) = self.dispatch_pair_join(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_MEET => {\n                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_PAIR_CODE_SUBMIT => {\n                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPEN => {\n                    if let Err(msg) = self.dispatch_net_stream_open(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SEND => {\n                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAMS => {\n                    if let Err(msg) = self.dispatch_net_streams(&send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_RETIRE => {\n                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_OPENER => {\n                    if let Err(msg) = self.dispatch_net_stream_opener(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_SESSIONS => {\n                    // Clone the (id, endpoint, log) out from under the sessions\n                    // lock, then read each log's cursor under the log lock alone —\n                    // never both locks at once (the dispatch_subscribe discipline).\n                    let rows: Vec<(u64, String, Arc<Mutex<OutputLog>>)> = {\n                        let sessions = recover(&self.sessions);\n                        sessions\n                            .iter()\n                            .map(|(id, h)| (*id, h.endpoint.clone(), Arc::clone(&h.log)))\n                            .collect()\n                    };\n                    // Snapshot each session's truth under its OWN log lock (cursor +\n                    // controller identity + the control/viewer stamp truth), doing NO\n                    // file I/O under the lock (the lock-across-effect discipline, KH\n                    // 7.12/5.16). The convergence re-assert + the reply then run OFF\n                    // the lock.\n                    struct SessSnap {\n                        id: u64,\n                        endpoint: String,\n                        resume_seq: u64,\n                        controller_by: Option<String>,\n                        has_controller: bool,\n                        viewers: u32,\n                    }\n                    let snaps: Vec<SessSnap> = rows\n                        .into_iter()\n                        .map(|(id, endpoint, log)| {\n                            let mut log = recover_log(&log);\n                            // B-2 (REQ-CONTROLLER-LIVENESS-REAP): make the controller\n                            // truth HONEST before reading it — reap a dead-writer\n                            // (severed) controller so both the reply's controller_by\n                            // AND the off-lock converge_perch_stamps below see the\n                            // cleared state and the stale info.json stamp clears.\n                            let _ = log.reap_dead_controller();\n                            SessSnap {\n                                id,\n                                endpoint,\n                                resume_seq: log.delivered_through(),\n                                controller_by: log.controller_by().map(str::to_string),\n                                has_controller: log.has_controller(),\n                                viewers: log.live_viewer_count(),\n                            }\n                        })\n                        .collect();\n                    // STAMP CONVERGENCE (REQ-HAZARD-CONTROL-STAMP-CONVERGENCE): re-assert\n                    // each live session's control/viewer stamps to broker truth,\n                    // divergence-gated + OFF the log lock. Closes the stamp-before-bind\n                    // loss for a picker-created endpoint (its spawn stamp was swallowed\n                    // pre-bind); converges within one reconcile-poll window after bind.\n                    // [impl->REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]\n                    for s in &snaps {\n                        converge_perch_stamps(\n                            &s.endpoint,\n                            s.controller_by.as_deref(),\n                            s.has_controller,\n                            s.viewers,\n                        );\n                    }\n                    let reply = SessionsReply {\n                        sessions: snaps\n                            .into_iter()\n                            .map(|s| SessionInfo {\n                                session_id: s.id,\n                                endpoint: s.endpoint,\n                                resume_seq: s.resume_seq,\n                                controller_by: s.controller_by,\n                            })\n                            .collect(),\n                    };\n                    let frame = Envelope::new(\n                        KIND_SESSIONS_REPLY,\n                        serde_json::to_value(reply).expect(\"SessionsReply serializes\"),\n                    );\n                    send_frame(&send, &frame);\n                }\n                KIND_ENDPOINT_INPUT => {\n                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\n                        send_error(&send, &msg);\n                    }\n                }\n                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\n                    Ok(id) => my_stream_subs.push(id),\n                    Err(msg) => send_error(&send, &msg),\n                },\n                KIND_NET_PRESENCE_SUBSCRIBE => {\n                    match self.dispatch_net_presence_subscribe(env, &send) {\n                        Ok(()) => my_presence_sub = true,\n                        Err(msg) => send_error(&send, &msg),\n                    }\n                }\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\n                // An unknown kind is tolerated (forward-compat): report, don't die.\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\n            }\n        }\n\n        // Brain gone: detach as subscriber but leave the sessions running.\n        let sessions = recover(&self.sessions);\n        for id in &my_subs {\n            if let Some(h) = sessions.get(id) {\n                recover_log(&h.log).detach_if(&send);\n            }\n        }\n        drop(sessions);\n        // Same for net streams: the streams + their rings persist (D4b).\n        if let Some(host) = self.net.get() {\n            for id in &my_stream_subs {\n                host.detach_stream_sub(*id, &send);\n            }\n            // And presence: the liveness log + its ring persist (D4c).\n            if my_presence_sub {\n                host.detach_presence_sub(&send);\n            }\n        }\n        Ok(())\n    }\n\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\n    /// 7.4): a hung child blocks only its own threads — shell instances ride\n    /// exactly this isolation (one owner's hung shell binary stalls neither\n    /// another owner's delivery nor the broker's control dispatch).\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\n    fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {\n        let req: SpawnReq =\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;\n        // Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the\n…\n    }\n\n[Showing lines 2939-3238 of 6063. Use :3239 to continue]"}],"details":{"truncation":{"content":"        };\r\n        sessions.values().any(|h| {\r\n            match h.log.lock() {\r\n                Ok(log) => log.local_controller_wedged(now, deadline),\r\n                // A poisoned log lock cannot be honestly read — treat as NOT wedged\r\n                // (fail toward promotion, never a permanent gate that strands apply).\r\n                Err(_) => false,\r\n            }\r\n        })\r\n    }\r\n\r\n    /// The count of live QUIC connections the broker-owned net endpoint holds\r\n    /// (D4a, ADR-0004 §B), or 0 when net-less. A direct read of the broker's own\r\n    /// state — the survival-assertion seam for the D7-1 process-level E2E: a QUIC\r\n    /// connection the broker holds must outlive a brain-PROCESS swap (restoration\r\n    /// D7-1), the net sibling of [`session_count`](Self::session_count).\r\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\r\n    pub fn net_conn_count(&self) -> usize {\r\n        self.net.get().map(|h| h.conn_count() as usize).unwrap_or(0)\r\n    }\r\n\r\n    /// The output-log high-water (`next_seq`) of a hosted session, or `None` if\r\n    /// absent. It advances every time the child emits output — the\r\n    /// **functional**-survival seam for the D7-1 process-level E2E (doyle F1):\r\n    /// after a brain-PROCESS swap, writing input to the broker-held PTY child and\r\n    /// watching THIS advance proves the path is alive end-to-end (input → child →\r\n    /// echo → broker ring), not merely that the handle is held. A read-only direct\r\n    /// peek, like [`session_pid`](Self::session_pid).\r\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\r\n    pub fn session_output_seq(&self, id: u64) -> Option<u64> {\r\n        self.sessions\r\n            .lock()\r\n            .unwrap()\r\n            .get(&id)\r\n            .map(|h| recover_log(&h.log).next_seq)\r\n    }\r\n\r\n    /// Drive one brain connection: handshake, then dispatch its command frames\r\n    /// until it disconnects. On disconnect, detach this connection from any\r\n    /// sessions it subscribed to — the sessions themselves persist (B2: a brain\r\n    /// restart must not disturb the hosted children).\r\n    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\r\n        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\r\n        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\r\n        struct HandlerTally<'a>(&'a AtomicUsize);\r\n        impl Drop for HandlerTally<'_> {\r\n            fn drop(&mut self) {\r\n                self.0.fetch_sub(1, Ordering::AcqRel);\r\n            }\r\n        }\r\n        self.conn_handlers.fetch_add(1, Ordering::AcqRel);\r\n        let _tally = HandlerTally(&self.conn_handlers);\r\n\r\n        let (mut recv, send) = conn.split();\r\n        // Round-4: the send half is owned by the bounded/cancelable conn\r\n        // primitive from birth — every write on this physical connection is\r\n        // deadline-bounded, and a wedge poisons + retires the conn.\r\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\r\n        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\r\n        // Base attribution fact (F-039 leg b): broker clients are brains; role\r\n        // facts (controller/viewer/presence/stream subscriber) accrete at the\r\n        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        send.describe(\"role=brain\");\r\n\r\n        // Version handshake: this connection must be a brain (2.3).\r\n        recv_hello(&mut recv, Role::Brain)?;\r\n\r\n        let mut my_subs: Vec<u64> = Vec::new();\r\n        let mut my_stream_subs: Vec<u64> = Vec::new();\r\n        let mut my_presence_sub = false;\r\n\r\n        loop {\r\n            let env = match read_frame(&mut recv) {\r\n                Ok(e) => e,\r\n                // Any read failure (EOF included) ends the connection.\r\n                Err(_) => break,\r\n            };\r\n            match env.kind.as_str() {\r\n                KIND_SPAWN => match self.dispatch_spawn(env, &send) {\r\n                    Ok(id) => my_subs.push(id),\r\n                    Err(msg) => send_error(&send, &msg),\r\n                },\r\n                KIND_SUBSCRIBE => match self.dispatch_subscribe(env, &send) {\r\n                    Ok(id) => my_subs.push(id),\r\n                    Err(msg) => send_error(&send, &msg),\r\n                },\r\n                KIND_UNSUBSCRIBE => {\r\n                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_INPUT => {\r\n                    if let Err(msg) = self.dispatch_input(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_RESIZE => {\r\n                    if let Err(msg) = self.dispatch_resize(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_KILL => {\r\n                    if let Err(msg) = self.dispatch_kill(env) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                // [impl->REQ-ADAPTER-LIVE-UPDATE]\r\n                KIND_ADAPTER_APPLY => {\r\n                    if let Err(msg) = self.dispatch_adapter_apply(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STATUS => self.dispatch_net_status(&send),\r\n                // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\r\n                KIND_BROKER_IMAGE => self.dispatch_broker_image(&send),\r\n                KIND_STALL_EVICTS => self.dispatch_stall_evicts(&send),\r\n                KIND_NET_DIAL => {\r\n                    if let Err(msg) = self.dispatch_net_dial(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_DIAL_LOOPBACK => {\r\n                    if let Err(msg) = self.dispatch_net_dial_loopback(&send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                // [impl->REQ-PUMP-PEER-ISOLATION]\r\n                KIND_NET_DIAL_SUBMIT => {\r\n                    if let Err(msg) = self.dispatch_net_dial_submit(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_PAIR_JOIN => {\r\n                    if let Err(msg) = self.dispatch_pair_join(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_PAIR_MEET => {\r\n                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_PAIR_CODE_SUBMIT => {\r\n                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_OPEN => {\r\n                    if let Err(msg) = self.dispatch_net_stream_open(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_SEND => {\r\n                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAMS => {\r\n                    if let Err(msg) = self.dispatch_net_streams(&send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_RETIRE => {\r\n                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_OPENER => {\r\n                    if let Err(msg) = self.dispatch_net_stream_opener(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_SESSIONS => {\r\n                    // Clone the (id, endpoint, log) out from under the sessions\r\n                    // lock, then read each log's cursor under the log lock alone —\r\n                    // never both locks at once (the dispatch_subscribe discipline).\r\n                    let rows: Vec<(u64, String, Arc<Mutex<OutputLog>>)> = {\r\n                        let sessions = recover(&self.sessions);\r\n                        sessions\r\n                            .iter()\r\n                            .map(|(id, h)| (*id, h.endpoint.clone(), Arc::clone(&h.log)))\r\n                            .collect()\r\n                    };\r\n                    // Snapshot each session's truth under its OWN log lock (cursor +\r\n                    // controller identity + the control/viewer stamp truth), doing NO\r\n                    // file I/O under the lock (the lock-across-effect discipline, KH\r\n                    // 7.12/5.16). The convergence re-assert + the reply then run OFF\r\n                    // the lock.\r\n                    struct SessSnap {\r\n                        id: u64,\r\n                        endpoint: String,\r\n                        resume_seq: u64,\r\n                        controller_by: Option<String>,\r\n                        has_controller: bool,\r\n                        viewers: u32,\r\n                    }\r\n                    let snaps: Vec<SessSnap> = rows\r\n                        .into_iter()\r\n                        .map(|(id, endpoint, log)| {\r\n                            let mut log = recover_log(&log);\r\n                            // B-2 (REQ-CONTROLLER-LIVENESS-REAP): make the controller\r\n                            // truth HONEST before reading it — reap a dead-writer\r\n                            // (severed) controller so both the reply's controller_by\r\n                            // AND the off-lock converge_perch_stamps below see the\r\n                            // cleared state and the stale info.json stamp clears.\r\n                            let _ = log.reap_dead_controller();\r\n                            SessSnap {\r\n                                id,\r\n                                endpoint,\r\n                                resume_seq: log.delivered_through(),\r\n                                controller_by: log.controller_by().map(str::to_string),\r\n                                has_controller: log.has_controller(),\r\n                                viewers: log.live_viewer_count(),\r\n                            }\r\n                        })\r\n                        .collect();\r\n                    // STAMP CONVERGENCE (REQ-HAZARD-CONTROL-STAMP-CONVERGENCE): re-assert\r\n                    // each live session's control/viewer stamps to broker truth,\r\n                    // divergence-gated + OFF the log lock. Closes the stamp-before-bind\r\n                    // loss for a picker-created endpoint (its spawn stamp was swallowed\r\n                    // pre-bind); converges within one reconcile-poll window after bind.\r\n                    // [impl->REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]\r\n                    for s in &snaps {\r\n                        converge_perch_stamps(\r\n                            &s.endpoint,\r\n                            s.controller_by.as_deref(),\r\n                            s.has_controller,\r\n                            s.viewers,\r\n                        );\r\n                    }\r\n                    let reply = SessionsReply {\r\n                        sessions: snaps\r\n                            .into_iter()\r\n                            .map(|s| SessionInfo {\r\n                                session_id: s.id,\r\n                                endpoint: s.endpoint,\r\n                                resume_seq: s.resume_seq,\r\n                                controller_by: s.controller_by,\r\n                            })\r\n                            .collect(),\r\n                    };\r\n                    let frame = Envelope::new(\r\n                        KIND_SESSIONS_REPLY,\r\n                        serde_json::to_value(reply).expect(\"SessionsReply serializes\"),\r\n                    );\r\n                    send_frame(&send, &frame);\r\n                }\r\n                KIND_ENDPOINT_INPUT => {\r\n                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\r\n                    Ok(id) => my_stream_subs.push(id),\r\n                    Err(msg) => send_error(&send, &msg),\r\n                },\r\n                KIND_NET_PRESENCE_SUBSCRIBE => {\r\n                    match self.dispatch_net_presence_subscribe(env, &send) {\r\n                        Ok(()) => my_presence_sub = true,\r\n                        Err(msg) => send_error(&send, &msg),\r\n                    }\r\n                }\r\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\r\n                // An unknown kind is tolerated (forward-compat): report, don't die.\r\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\r\n            }\r\n        }\r\n\r\n        // Brain gone: detach as subscriber but leave the sessions running.\r\n        let sessions = recover(&self.sessions);\r\n        for id in &my_subs {\r\n            if let Some(h) = sessions.get(id) {\r\n                recover_log(&h.log).detach_if(&send);\r\n            }\r\n        }\r\n        drop(sessions);\r\n        // Same for net streams: the streams + their rings persist (D4b).\r\n        if let Some(host) = self.net.get() {\r\n            for id in &my_stream_subs {\r\n                host.detach_stream_sub(*id, &send);\r\n            }\r\n            // And presence: the liveness log + its ring persist (D4c).\r\n            if my_presence_sub {\r\n                host.detach_presence_sub(&send);\r\n            }\r\n        }\r\n        Ok(())\r\n    }\r\n\r\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\r\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\r\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\r\n    /// 7.4): a hung child blocks only its own threads — shell instances ride\r\n    /// exactly this isolation (one owner's hung shell binary stalls neither\r\n    /// another owner's delivery nor the broker's control dispatch).\r\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\r\n    fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {\r\n        let req: SpawnReq =\r\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;\r\n        // Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the\r","truncated":true,"truncatedBy":"lines","totalLines":3125,"totalBytes":14837,"outputLines":300,"outputBytes":14837,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub fn any_local_controller_wedged(&self) -> bool {\n…\n        let sessions = match self.sessions.lock() {\n…\n        };\r\n        sessions.values().any(|h| {\r\n            match h.log.lock() {\r\n                Ok(log) => log.local_controller_wedged(now, deadline),\r\n                // A poisoned log lock cannot be honestly read — treat as NOT wedged\r\n                // (fail toward promotion, never a permanent gate that strands apply).\r\n                Err(_) => false,\r\n            }\r\n        })\r\n    }\r\n\r\n    /// The count of live QUIC connections the broker-owned net endpoint holds\r\n    /// (D4a, ADR-0004 §B), or 0 when net-less. A direct read of the broker's own\r\n    /// state — the survival-assertion seam for the D7-1 process-level E2E: a QUIC\r\n    /// connection the broker holds must outlive a brain-PROCESS swap (restoration\r\n    /// D7-1), the net sibling of [`session_count`](Self::session_count).\r\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\r\n    pub fn net_conn_count(&self) -> usize {\r\n        self.net.get().map(|h| h.conn_count() as usize).unwrap_or(0)\r\n    }\r\n\r\n    /// The output-log high-water (`next_seq`) of a hosted session, or `None` if\r\n    /// absent. It advances every time the child emits output — the\r\n    /// **functional**-survival seam for the D7-1 process-level E2E (doyle F1):\r\n    /// after a brain-PROCESS swap, writing input to the broker-held PTY child and\r\n    /// watching THIS advance proves the path is alive end-to-end (input → child →\r\n    /// echo → broker ring), not merely that the handle is held. A read-only direct\r\n    /// peek, like [`session_pid`](Self::session_pid).\r\n    // [impl->REQ-HAZARD-BROKER-PROCESS-ISOLATION]\r\n    pub fn session_output_seq(&self, id: u64) -> Option<u64> {\r\n        self.sessions\r\n            .lock()\r\n            .unwrap()\r\n            .get(&id)\r\n            .map(|h| recover_log(&h.log).next_seq)\r\n    }\r\n\r\n    /// Drive one brain connection: handshake, then dispatch its command frames\r\n    /// until it disconnects. On disconnect, detach this connection from any\r\n    /// sessions it subscribed to — the sessions themselves persist (B2: a brain\r\n    /// restart must not disturb the hosted children).\r\n    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {\r\n        // RAII handler tally (panic-safe): the round-4 gate's seam for \"the\r\n        // poisoned conn's handler reached EOF cleanup\" (see conn_handler_count).\r\n        struct HandlerTally<'a>(&'a AtomicUsize);\r\n        impl Drop for HandlerTally<'_> {\r\n            fn drop(&mut self) {\r\n                self.0.fetch_sub(1, Ordering::AcqRel);\r\n            }\r\n        }\r\n        self.conn_handlers.fetch_add(1, Ordering::AcqRel);\r\n        let _tally = HandlerTally(&self.conn_handlers);\r\n\r\n        let (mut recv, send) = conn.split();\r\n        // Round-4: the send half is owned by the bounded/cancelable conn\r\n        // primitive from birth — every write on this physical connection is\r\n        // deadline-bounded, and a wedge poisons + retires the conn.\r\n        // [impl->REQ-HAZARD-SHAREDSEND-NO-BLOCKING-WRITE-UNDER-LOCK]\r\n        let send: SharedSend = Arc::new(BrokerConn::new(send, brain_write_deadline()));\r\n        // Base attribution fact (F-039 leg b): broker clients are brains; role\r\n        // facts (controller/viewer/presence/stream subscriber) accrete at the\r\n        // attach sites. [impl->REQ-CONN-POISON-ATTRIBUTION]\r\n        send.describe(\"role=brain\");\r\n\r\n        // Version handshake: this connection must be a brain (2.3).\r\n        recv_hello(&mut recv, Role::Brain)?;\r\n\r\n        let mut my_subs: Vec<u64> = Vec::new();\r\n        let mut my_stream_subs: Vec<u64> = Vec::new();\r\n        let mut my_presence_sub = false;\r\n\r\n        loop {\r\n            let env = match read_frame(&mut recv) {\r\n                Ok(e) => e,\r\n                // Any read failure (EOF included) ends the connection.\r\n                Err(_) => break,\r\n            };\r\n            match env.kind.as_str() {\r\n                KIND_SPAWN => match self.dispatch_spawn(env, &send) {\r\n                    Ok(id) => my_subs.push(id),\r\n                    Err(msg) => send_error(&send, &msg),\r\n                },\r\n                KIND_SUBSCRIBE => match self.dispatch_subscribe(env, &send) {\r\n                    Ok(id) => my_subs.push(id),\r\n                    Err(msg) => send_error(&send, &msg),\r\n                },\r\n                KIND_UNSUBSCRIBE => {\r\n                    if let Err(msg) = self.dispatch_unsubscribe(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_INPUT => {\r\n                    if let Err(msg) = self.dispatch_input(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_RESIZE => {\r\n                    if let Err(msg) = self.dispatch_resize(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_KILL => {\r\n                    if let Err(msg) = self.dispatch_kill(env) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                // [impl->REQ-ADAPTER-LIVE-UPDATE]\r\n                KIND_ADAPTER_APPLY => {\r\n                    if let Err(msg) = self.dispatch_adapter_apply(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STATUS => self.dispatch_net_status(&send),\r\n                // [impl->REQ-UPDATE-RUNNING-IMAGE-SURFACE]\r\n                KIND_BROKER_IMAGE => self.dispatch_broker_image(&send),\r\n                KIND_STALL_EVICTS => self.dispatch_stall_evicts(&send),\r\n                KIND_NET_DIAL => {\r\n                    if let Err(msg) = self.dispatch_net_dial(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_DIAL_LOOPBACK => {\r\n                    if let Err(msg) = self.dispatch_net_dial_loopback(&send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                // [impl->REQ-PUMP-PEER-ISOLATION]\r\n                KIND_NET_DIAL_SUBMIT => {\r\n                    if let Err(msg) = self.dispatch_net_dial_submit(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_PAIR_JOIN => {\r\n                    if let Err(msg) = self.dispatch_pair_join(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_PAIR_MEET => {\r\n                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_PAIR_CODE_SUBMIT => {\r\n                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_OPEN => {\r\n                    if let Err(msg) = self.dispatch_net_stream_open(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_SEND => {\r\n                    if let Err(msg) = self.dispatch_net_stream_send(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAMS => {\r\n                    if let Err(msg) = self.dispatch_net_streams(&send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_RETIRE => {\r\n                    if let Err(msg) = self.dispatch_net_stream_retire(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_OPENER => {\r\n                    if let Err(msg) = self.dispatch_net_stream_opener(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_SESSIONS => {\r\n                    // Clone the (id, endpoint, log) out from under the sessions\r\n                    // lock, then read each log's cursor under the log lock alone —\r\n                    // never both locks at once (the dispatch_subscribe discipline).\r\n                    let rows: Vec<(u64, String, Arc<Mutex<OutputLog>>)> = {\r\n                        let sessions = recover(&self.sessions);\r\n                        sessions\r\n                            .iter()\r\n                            .map(|(id, h)| (*id, h.endpoint.clone(), Arc::clone(&h.log)))\r\n                            .collect()\r\n                    };\r\n                    // Snapshot each session's truth under its OWN log lock (cursor +\r\n                    // controller identity + the control/viewer stamp truth), doing NO\r\n                    // file I/O under the lock (the lock-across-effect discipline, KH\r\n                    // 7.12/5.16). The convergence re-assert + the reply then run OFF\r\n                    // the lock.\r\n                    struct SessSnap {\r\n                        id: u64,\r\n                        endpoint: String,\r\n                        resume_seq: u64,\r\n                        controller_by: Option<String>,\r\n                        has_controller: bool,\r\n                        viewers: u32,\r\n                    }\r\n                    let snaps: Vec<SessSnap> = rows\r\n                        .into_iter()\r\n                        .map(|(id, endpoint, log)| {\r\n                            let mut log = recover_log(&log);\r\n                            // B-2 (REQ-CONTROLLER-LIVENESS-REAP): make the controller\r\n                            // truth HONEST before reading it — reap a dead-writer\r\n                            // (severed) controller so both the reply's controller_by\r\n                            // AND the off-lock converge_perch_stamps below see the\r\n                            // cleared state and the stale info.json stamp clears.\r\n                            let _ = log.reap_dead_controller();\r\n                            SessSnap {\r\n                                id,\r\n                                endpoint,\r\n                                resume_seq: log.delivered_through(),\r\n                                controller_by: log.controller_by().map(str::to_string),\r\n                                has_controller: log.has_controller(),\r\n                                viewers: log.live_viewer_count(),\r\n                            }\r\n                        })\r\n                        .collect();\r\n                    // STAMP CONVERGENCE (REQ-HAZARD-CONTROL-STAMP-CONVERGENCE): re-assert\r\n                    // each live session's control/viewer stamps to broker truth,\r\n                    // divergence-gated + OFF the log lock. Closes the stamp-before-bind\r\n                    // loss for a picker-created endpoint (its spawn stamp was swallowed\r\n                    // pre-bind); converges within one reconcile-poll window after bind.\r\n                    // [impl->REQ-HAZARD-CONTROL-STAMP-CONVERGENCE]\r\n                    for s in &snaps {\r\n                        converge_perch_stamps(\r\n                            &s.endpoint,\r\n                            s.controller_by.as_deref(),\r\n                            s.has_controller,\r\n                            s.viewers,\r\n                        );\r\n                    }\r\n                    let reply = SessionsReply {\r\n                        sessions: snaps\r\n                            .into_iter()\r\n                            .map(|s| SessionInfo {\r\n                                session_id: s.id,\r\n                                endpoint: s.endpoint,\r\n                                resume_seq: s.resume_seq,\r\n                                controller_by: s.controller_by,\r\n                            })\r\n                            .collect(),\r\n                    };\r\n                    let frame = Envelope::new(\r\n                        KIND_SESSIONS_REPLY,\r\n                        serde_json::to_value(reply).expect(\"SessionsReply serializes\"),\r\n                    );\r\n                    send_frame(&send, &frame);\r\n                }\r\n                KIND_ENDPOINT_INPUT => {\r\n                    if let Err(msg) = self.dispatch_endpoint_input(env, &send) {\r\n                        send_error(&send, &msg);\r\n                    }\r\n                }\r\n                KIND_NET_STREAM_SUBSCRIBE => match self.dispatch_net_stream_subscribe(env, &send) {\r\n                    Ok(id) => my_stream_subs.push(id),\r\n                    Err(msg) => send_error(&send, &msg),\r\n                },\r\n                KIND_NET_PRESENCE_SUBSCRIBE => {\r\n                    match self.dispatch_net_presence_subscribe(env, &send) {\r\n                        Ok(()) => my_presence_sub = true,\r\n                        Err(msg) => send_error(&send, &msg),\r\n                    }\r\n                }\r\n                KIND_BRAIN_RESTART => self.dispatch_brain_restart(&send),\r\n                // An unknown kind is tolerated (forward-compat): report, don't die.\r\n                other => send_error(&send, &format!(\"unknown command kind {other:?}\")),\r\n            }\r\n        }\r\n\r\n        // Brain gone: detach as subscriber but leave the sessions running.\r\n        let sessions = recover(&self.sessions);\r\n        for id in &my_subs {\r\n            if let Some(h) = sessions.get(id) {\r\n                recover_log(&h.log).detach_if(&send);\r\n            }\r\n        }\r\n        drop(sessions);\r\n        // Same for net streams: the streams + their rings persist (D4b).\r\n        if let Some(host) = self.net.get() {\r\n            for id in &my_stream_subs {\r\n                host.detach_stream_sub(*id, &send);\r\n            }\r\n            // And presence: the liveness log + its ring persist (D4c).\r\n            if my_presence_sub {\r\n                host.detach_presence_sub(&send);\r\n            }\r\n        }\r\n        Ok(())\r\n    }\r\n\r\n    /// Spawn a child under a fresh PTY, register it, auto-subscribe the spawning\r\n    /// connection, start its output drain and exit waiter, and reply `spawned`.\r\n    /// Every hosted session gets its **own** drain + exit-waiter threads (KH\r\n    /// 7.4): a hung child blocks only its own threads — shell instances ride\r\n    /// exactly this isolation (one owner's hung shell binary stalls neither\r\n    /// another owner's delivery nor the broker's control dispatch).\r\n    // [impl->REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]\r\n    fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result<u64, String> {\r\n        let req: SpawnReq =\r\n            serde_json::from_value(env.payload).map_err(|e| format!(\"bad spawn payload: {e}\"))?;\r\n        // Single-flight WAKE (W4, REQ-SPAWN-COLLISION-GUARD-LIVE-DUP): the broker is the\r\n…\n    }","startLine":2933,"lineNumbers":[2933,null,2936,null,2939,2940,2941,2942,2943,2944,2945,2946,2947,2948,2949,2950,2951,2952,2953,2954,2955,2956,2957,2958,2959,2960,2961,2962,2963,2964,2965,2966,2967,2968,2969,2970,2971,2972,2973,2974,2975,2976,2977,2978,2979,2980,2981,2982,2983,2984,2985,2986,2987,2988,2989,2990,2991,2992,2993,2994,2995,2996,2997,2998,2999,3000,3001,3002,3003,3004,3005,3006,3007,3008,3009,3010,3011,3012,3013,3014,3015,3016,3017,3018,3019,3020,3021,3022,3023,3024,3025,3026,3027,3028,3029,3030,3031,3032,3033,3034,3035,3036,3037,3038,3039,3040,3041,3042,3043,3044,3045,3046,3047,3048,3049,3050,3051,3052,3053,3054,3055,3056,3057,3058,3059,3060,3061,3062,3063,3064,3065,3066,3067,3068,3069,3070,3071,3072,3073,3074,3075,3076,3077,3078,3079,3080,3081,3082,3083,3084,3085,3086,3087,3088,3089,3090,3091,3092,3093,3094,3095,3096,3097,3098,3099,3100,3101,3102,3103,3104,3105,3106,3107,3108,3109,3110,3111,3112,3113,3114,3115,3116,3117,3118,3119,3120,3121,3122,3123,3124,3125,3126,3127,3128,3129,3130,3131,3132,3133,3134,3135,3136,3137,3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154,3155,3156,3157,3158,3159,3160,3161,3162,3163,3164,3165,3166,3167,3168,3169,3170,3171,3172,3173,3174,3175,3176,3177,3178,3179,3180,3181,3182,3183,3184,3185,3186,3187,3188,3189,3190,3191,3192,3193,3194,3195,3196,3197,3198,3199,3200,3201,3202,3203,3204,3205,3206,3207,3208,3209,3210,3211,3212,3213,3214,3215,3216,3217,3218,3219,3220,3221,3222,3223,3224,3225,3226,3227,3228,3229,3230,3231,3232,3233,3234,3235,3236,3237,3238,null,3449]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\broker.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":6063,"totalBytes":14837,"outputLines":300,"outputBytes":14837,"shownRange":{"start":2939,"end":3238},"nextOffset":3239}}},"isError":false,"timestamp":1784247549262}}
{"type":"message","id":"0cd49b84","parentId":"c2bef99d","timestamp":"2026-07-17T00:19:12.519Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning transport search without file glob**"},{"type":"toolCall","id":"call_hnz4lCaHiq0uRvncok1CE4wM|fc_0ceecce325b7fe2d016a5974ff988c8198a6959b6dbf76ba74","name":"grep","arguments":{"i":"Locating local transport stream","pattern":"pub enum Stream|struct Stream|pub enum SendHalf|pub fn split\\(self\\)","path":"crates","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":8902,"output":87,"cacheRead":53760,"cacheWrite":0,"totalTokens":62749,"reasoningTokens":18,"cost":{"input":0.04451,"output":0.00261,"cacheRead":0.026879999999999998,"cacheWrite":0,"total":0.074}},"stopReason":"toolUse","timestamp":1784247549312,"responseId":"resp_0ceecce325b7fe2d016a5974fd888481989a9907ce7a57c379","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5974fe78688198a17d03cb3f5fa7f4","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXT_Kvu2je1u14PkomT_W0rg1I3EFgE9jnqJAaGzDFMV80c7efcIWptepFf-mJTd12T4RQOhaUOuyBVP4gBRd_FvgvU3q1hSA9uOyEjpJ8RBI57E7FQ-IAe4kogDSljAb4hEhFqEDgqWM_pUixiPQNggYOAn9QApkZfGdxP8nMtZlFyj96Xb1TaaEg6Fb-ssZy1_ZdLtexAANlU8_QWlcP93BEsXnsrSrseOu72v0wuvDvMZpy9vhaOFh77y0M-KrVh6eJRVJFhEA9uDWGAhQB7rituXhqPLBi1_MVisKBL9HSMstHpoffn11BmJYFLU1HuiTQ84vKCORmZdvZrbs-yF6sFRc7BJmaj0L0HXIe1ddXMUGRj3Z-CEoAduKBfvdoeSuUmPp8uSmG87kdxCnQ7j9MmUBFb3xcdNYa9VsN9xUPpE8bPmZZ0CA0jHeY_pVGWPUtVoV53rrI91JGJ-9s2x_mbA4Apzdueg8eQOdV6YetXdJXdYfXyHmnTE4--YNGl3lgRwPmJiYrM0f-jSao2L0FBi4_JtXyp74nwFmoyDdhZBxws1D1Dqc8Dk8csSCVku-qS4RuERNoPodfgtgxt-x3cPY_NOSTKlnFzCo5uRI_-ugx6Hq29TP8iJVONp3IrzT9ghtugzvpIgDr5YaQYWIeUfv1q2oA-9PAlb_SRWgZntTxfKK6k8Q7bAle4iQwAdEFq719qrJCOB-dgLDQRHrmQ6kkXN8tdumDpHAg5TkvtcUgUohFQGn9vWoWZv8XF1EGe22k0ZKstyuYBci4qjU8A1j29EYIXPbXYocSVVwAZUnYwmkSIs3_AxLsNhgE8HvchBLgsWS-6FBXLSxQaWs1D3avnl2uVItEAUjYthVYehSnoSuM2ITeurIsbwppv9IbnltMMWkm5FSPbM5bRJZ791E9jzCFN7PjRxVOrMpXKcuULz-RdEgrcWcqxKLWcFwTDsXaKcJvztREjwFk9MCmZ6DuHkwMN2xKFI57SO_VP4l14_KCXwYZDCbNTMuRMy","summary":[{"type":"summary_text","text":"**Planning transport search without file glob**"}]},{"id":"fc_0ceecce325b7fe2d016a5974ff988c8198a6959b6dbf76ba74","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating local transport stream\",\"pattern\":\"pub enum Stream|struct Stream|pub enum SendHalf|pub fn split\\\\(self\\\\)\",\"path\":\"crates\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_hnz4lCaHiq0uRvncok1CE4wM","name":"grep"}]},"duration":3190.443400003016,"ttft":1151.9538000002503,"contextSnapshot":{"promptTokens":62662,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_hnz4lCaHiq0uRvncok1CE4wM|fc_0ceecce325b7fe2d016a5974ff988c8198a6959b6dbf76ba74","toolName":"grep","startedAt":"2026-07-17T00:19:12.519Z","args":{"path":"crates"},"intent":"Locating local transport stream"},"id":"2ea47ffd","parentId":"0cd49b84","timestamp":"2026-07-17T00:19:12.519Z"}
{"type":"message","id":"3eb8b4b7","parentId":"2ea47ffd","timestamp":"2026-07-17T00:19:12.534Z","message":{"role":"toolResult","toolCallId":"call_hnz4lCaHiq0uRvncok1CE4wM|fc_0ceecce325b7fe2d016a5974ff988c8198a6959b6dbf76ba74","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## dispatch.rs\n 90|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n*91|pub enum StreamFamily {\n 92|    /// A context sync pull (`SyncRecord::Request`).\n 93|    Sync,\n 94|    /// An update pull (`UpdRecord::Query`).\n## nethost.rs\n 178|// [impl->REQ-DAEMON-2]\n*179|struct StreamLog {\n 180|    stream_id: u64,\n 181|    /// TRANSPORT read counter: bumped once per [`append`](Self::append) (one\n 182|    /// read-pump `read_chunk`), INDEPENDENT of byte size. NOT a logical-chunk\n...\n 521|/// writes ride it over IPC) + the sequenced read log.\n*522|struct StreamEntry {\n 523|    conn_id: u64,\n 524|    remote_id_hex: String,\n 525|    /// Whether THIS node opened the stream (`open_stream`) vs the peer"}],"details":{"scopePath":"crates","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":3,"fileCount":2,"files":["crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":1},{"path":"crates/spt-daemon/src/nethost.rs","count":2}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## dispatch.rs\n 90│#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n*91│pub enum StreamFamily {\n 92│    /// A context sync pull (`SyncRecord::Request`).\n 93│    Sync,\n 94│    /// An update pull (`UpdRecord::Query`).\n## nethost.rs\n 178│// [impl->REQ-DAEMON-2]\n*179│struct StreamLog {\n 180│    stream_id: u64,\n 181│    /// TRANSPORT read counter: bumped once per [`append`](Self::append) (one\n 182│    /// read-pump `read_chunk`), INDEPENDENT of byte size. NOT a logical-chunk\n    │...\n 521│/// writes ride it over IPC) + the sequenced read log.\n*522│struct StreamEntry {\n 523│    conn_id: u64,\n 524│    remote_id_hex: String,\n 525│    /// Whether THIS node opened the stream (`open_stream`) vs the peer"},"isError":false,"timestamp":1784247552534}}
{"type":"message","id":"9736762c","parentId":"3eb8b4b7","timestamp":"2026-07-17T00:19:18.429Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":""},{"type":"toolCall","id":"call_SPAoxjPmHT3zsmoSVni9df4Q|fc_0ceecce325b7fe2d016a597504ff3c819886eda4c1ea126ab2","name":"grep","arguments":{"i":"Finding transport imports","pattern":"use .*transport|LocalSocket|interprocess","path":"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/conn.rs","selector":"1-100","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3078,"output":87,"cacheRead":59904,"cacheWrite":0,"totalTokens":63069,"reasoningTokens":11,"cost":{"input":0.015390000000000001,"output":0.00261,"cacheRead":0.029952,"cacheWrite":0,"total":0.047952}},"stopReason":"toolUse","timestamp":1784247552551,"responseId":"resp_0ceecce325b7fe2d016a597500da4c81988c767f7c5b7262f3","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5975046c188198ae24a29a7701b41a","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXUF2wMsMaUp5qr0Q5uv0H_5rDTEBnD8Y6E1LdclJZhKAyr3gody4ArDOvWBb6FNBfRfdEHqQdFCVhF1nWup8oo9qIw1rOAHQPIX6XqZ3XS2_7lRaOoM-kWekMjO7RK59OlFb1r02xTbNxY5MlWStg9Scnaw3iDiurq26s5UhET0xZMVdeOwSISQ1NAzs1Oa4XgzeavxdmpJoffPhUv0SyN8b0nswhrs63IJyn6-f6u9OaeDMwtIF0AcKtH-9wcPnoNCXhSSAlo49UQgD4P7hZ5dzxSmx_s1cvjtOXJaPlIkFsnOn1mH7mR1dQYlmPRQFK--BU10GbkO8NY7vmHdYvO5_zVvQLegfZuj09m7sf5aY7mTPAJPFc_d85CNWIy-r3hSQ1nsFpJnoAX8E-XpQFbg9rhJO6wrWLddBZKuMencqNXr2uiaqFXvzhG0bOfFRa5irBGQxdoCDzTAvwXs8HNYyDQai8y3uWzG90fEl1bJZhcEVeAqW8Yyttj8ydVvjGN8yrsY-Eehd4RqaVjkDvqMx-FjzF2Nhhm9m3qkGrH0xoLrWa7bc-zHoJOkNct4oJUSltFOFFXcJmDdkEjKrtUYHaLXQsb9zOdXPxT67Hy0gAIf15aaLZnxzUV5j-OfYQk5-sW8mv4TFsyOpelRK0F0RBtnhaRetl3a3k5Q2C7cdEbRZmLOGgp66WHIPD4NwHxaoXaYZCwYrXdTqJjFjjNNz5zkiKAnctY0PRAxjKh-oHEi9y8HZ5xSVQUqQS0n3p94Cb2FSCNsy9TpKw0XXnDe4gApXLWWZctFU3ShMARiKR4xGwpr6WqdTm5EuVUBZIWj8d11JJDAzeUDpbPQ7UH1VMmtNiXFsHmYmsawtfJcWuE6a10DwXAGRLy0PDYygd3Vun7p2KjygSy6fNKf2-8P-46Wq5ky9-6mfCf_p_jU_EDJqYifYzUAdRN_lXORYI3l","summary":[]},{"id":"fc_0ceecce325b7fe2d016a597504ff3c819886eda4c1ea126ab2","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding transport imports\",\"pattern\":\"use .*transport|LocalSocket|interprocess\",\"path\":\"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/conn.rs\",\"selector\":\"1-100\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_SPAoxjPmHT3zsmoSVni9df4Q","name":"grep"}]},"duration":5872.827399998903,"ttft":3883.8144000023603,"contextSnapshot":{"promptTokens":62982,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_SPAoxjPmHT3zsmoSVni9df4Q|fc_0ceecce325b7fe2d016a597504ff3c819886eda4c1ea126ab2","toolName":"grep","startedAt":"2026-07-17T00:19:18.429Z","args":{"path":"crates/spt-daemon/src/broker.rs; crates/spt-daemon/src/conn.rs"},"intent":"Finding transport imports"},"id":"40eeda8d","parentId":"9736762c","timestamp":"2026-07-17T00:19:18.429Z"}
{"type":"message","id":"9706a1e8","parentId":"40eeda8d","timestamp":"2026-07-17T00:19:18.434Z","message":{"role":"toolResult","toolCallId":"call_SPAoxjPmHT3zsmoSVni9df4Q|fc_0ceecce325b7fe2d016a597504ff3c819886eda4c1ea126ab2","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/src/\n## broker.rs\n 41|\n*42|use interprocess::local_socket::{prelude::*, Stream};\n 43|use spt_term::{Drain, PtySession, ScreenGrid, SessionSurface, SurfaceSize};\n 44|\n 45|use spt_net::net::attach::AttachIntent;\n...\n 72|use crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\n*73|use crate::transport::{recv_hello, DaemonTransport, LocalSocketTransport};\n 74|\n 75|/// The shared, serialized send side of one brain connection. Output frames,\n 76|/// replay frames, command acks, and exit/error events all write through this.\n## conn.rs\n 6|//! A broker writer used to hold the connection's `Mutex<SendHalf>` guard\n*7|//! ACROSS a blocking [`write_frame`]. On Windows, `interprocess 2.4.2` routes\n 8|//! that send to `WriteFileEx` + `SleepEx(INFINITE, alertable)` — a local-socket\n 9|//! path with **no supported write timeout** (`set_timeout` → `Unsupported`).\n 10|//! A connected consumer that stops reading (an `rc --take` whose process\n...\n 51|//! client off, so the old client's next access fails, a re-issued server read\n*52|//! fails, and the `interprocess` drop-limbo flush cannot block on unread\n 53|//! data), followed by one more `CancelIoEx` covering a read re-issued in the\n 54|//! cancel→disconnect window.\n 55|//!\n...\n 67|\n*68|use interprocess::local_socket::SendHalf;\n 69|\n 70|use crate::codec::write_frame;\n 71|use crate::frame::Envelope;"}],"details":{"scopePath":"crates/spt-daemon/src/broker.rs, crates/spt-daemon/src/conn.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":5,"fileCount":2,"files":["crates/spt-daemon/src/broker.rs","crates/spt-daemon/src/conn.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/broker.rs","count":2},{"path":"crates/spt-daemon/src/conn.rs","count":3}],"truncated":false,"displayContent":"# crates/spt-daemon/src/\n## broker.rs\n 41│\n*42│use interprocess::local_socket::{prelude::*, Stream};\n 43│use spt_term::{Drain, PtySession, ScreenGrid, SessionSurface, SurfaceSize};\n 44│\n 45│use spt_net::net::attach::AttachIntent;\n   │...\n 72│use crate::translation::{key_to_bytes, InjectFloor, KeyCmd, ToBinary, TranslationChild};\n*73│use crate::transport::{recv_hello, DaemonTransport, LocalSocketTransport};\n 74│\n 75│/// The shared, serialized send side of one brain connection. Output frames,\n 76│/// replay frames, command acks, and exit/error events all write through this.\n## conn.rs\n  6│//! A broker writer used to hold the connection's `Mutex<SendHalf>` guard\n *7│//! ACROSS a blocking [`write_frame`]. On Windows, `interprocess 2.4.2` routes\n  8│//! that send to `WriteFileEx` + `SleepEx(INFINITE, alertable)` — a local-socket\n  9│//! path with **no supported write timeout** (`set_timeout` → `Unsupported`).\n 10│//! A connected consumer that stops reading (an `rc --take` whose process\n   │...\n 51│//! client off, so the old client's next access fails, a re-issued server read\n*52│//! fails, and the `interprocess` drop-limbo flush cannot block on unread\n 53│//! data), followed by one more `CancelIoEx` covering a read re-issued in the\n 54│//! cancel→disconnect window.\n 55│//!\n   │...\n 67│\n*68│use interprocess::local_socket::SendHalf;\n 69│\n 70│use crate::codec::write_frame;\n 71│use crate::frame::Envelope;"},"isError":false,"timestamp":1784247558433}}
{"type":"message","id":"1b70c5a1","parentId":"9706a1e8","timestamp":"2026-07-17T00:19:27.867Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing delayed completion after poison**\n\n**Investigating client-side receiver thread failure**"},{"type":"toolCall","id":"call_efCTTFSpiK2LhfXv3yjFrHfX|fc_0ceecce325b7fe2d016a59750f84f08198b34c55950395c879","name":"read","arguments":{"i":"Reading brain reader and deadlines","path":"crates/spt-daemon/src/brain.rs","selector":"200-330"}},{"type":"toolCall","id":"call_9JNAfTp6onQjC7by8QqHcGxp|fc_0ceecce325b7fe2d016a59750f85008198886eeeea17f0b256","name":"read","arguments":{"i":"Reading brain event blocking","path":"crates/spt-daemon/src/brain.rs","selector":"830-930"}},{"type":"toolCall","id":"call_KVFdamhW76ElYpC5YcPhkQZA|fc_0ceecce325b7fe2d016a59750f850c81988e6804b871747db7","name":"grep","arguments":{"i":"Locating dispatch attach serve","pattern":"StreamFamily::Attach|serve_attach|AttachServeOutcome|finished","path":"crates/spt-daemon/src/dispatch.rs","selector":"460-620","case":true,"gitignore":true,"skip":null}},{"type":"toolCall","id":"call_aBEdVKShOml9hLFDTyhxLmNp|fc_0ceecce325b7fe2d016a59750f851c819886270d56c98959bd","name":"grep","arguments":{"i":"Checking poison lifecycle tests","pattern":"DISPATCH|poison|subscriber|stream-sub","path":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/brain_decouple.rs","selector":null,"case":false,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3576,"output":430,"cacheRead":59904,"cacheWrite":0,"totalTokens":63910,"reasoningTokens":184,"cost":{"input":0.01788,"output":0.0129,"cacheRead":0.029952,"cacheWrite":0,"total":0.060732}},"stopReason":"toolUse","timestamp":1784247558445,"responseId":"resp_0ceecce325b7fe2d016a597506c0c88198a8541f8a71adb3bf","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a597507a8248198991e565163d87115","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXUP-aqtj5SfikJu4-8ShGutEaAIfymgOrQ5VRoqGrhuf2sgmjZfN9gQbzk_VRvnhhuYWT0ovkFaMH9o7NM4Qe_okk2kS8b117FWC4Or3THyN1caYpKWFkyIgcqLlhAJcTh8v7TwXk8Gjcf8fLJZlP7njZkwO2lvf_212_sx887A_iEujZRp1_hRI6q-2H5CJYFFBnAchdJ775myM3QeqHG96_wzNHpm3TUtO7nSvEhQN8DVw5f51ragI8gdA2bHp-R45RaJst1RlZV4tPdreOqNeX9Y6PUao3J09lLz5nvefN_nthhiLKbQyHd4iXEVRxRi1_2GJAy0pDdMtr650GEOp8XFlXqTBbLZUL6KoxRZ5F3jjbaX2gZMUixS3rTAxa_hGkSnosCt01d-dSjBnUWyl2Kbqv0PiK1EROoIajvXUxrAOYPOjetC7L05-rVcr0zcSPz0QvUKvU2UwJdFZoX5yjGVwtqEaTitbKqy6-xDh4Z73zlj7ih7fab1p7IGTbfT8HdWDXUQYjMKOgjl-_ptMhOvhnogGX-UWd6yd8beqtoFPTYvxytY731JOQyOhi3CCY8aoukQzQHeN9Jg_javqZeUy4RlWuAaQBadCyomZRJArts6LoVV3VxVbqCqrVhHTZCSoOWZiqUTUicA0vHHYT2SUPlDJnjltwYd0XEMha8u3TbWvpLpRCUQ6cFmtN05FFw_jZShVdbK2UFOpHiy6NXxzQutkukLtMPXYBb6kKR8jx5YoCW6kzCAteJTZzorP4kYMknli71dA6XCM4HDvTPgCJ40bjeUNZKwlWrkFtOcHTa3MsD_sgN_Cd7n56HfPfFOaj3cTMJRCExCj2c_1DbYFR8RT171tovfFq1rzy2JjDtEcoxIYR-v2DwWNNwFxH7Yuq8JteoSw3Yewx30Tk58tPvIVZfIUAn9P4ojuFRXZPqtQVE-S7Cm_0nssym3o9jB6rNus21qiuZSewYCuKoDHQzGnnomi4omFfBoZQa7lkNl4qBF00wxWaBd0ZrpGn3W6z12MU6qKyYc8pb1_LSfMlSkMFowAy7B9eJzS43lRx1Dsr9WXDvviQsqOusSxIKrYSVgsfEtAmNOohHdRqha1WdHExPMLofTEoxbV7XoYUIUET-rdwcnCduzvonLx5ZhSeTZfgZf30MqhmXnpeJyqQ5jgpscfnmHbtsOJEv4-vvjMkfam13OZFBxDNpuT7otwLS_y69HEWAjkU-mtWkrbj82SWiQSHJpxX2McgdtdPoXtiO6wPCn90MKDNvoliCl30B3KoeYdNAIhH6DtqY8imLhg4kjur5rVlIYTW3H2aUvj4Vn9J--IN3uNUfsLBRasMfu581WU-0GE1ZIfu3x5TIA5rRmnwMSSTTbfC4jyvhkPTeTmAtyQsWpF-QqJPpnK4mGd74EuMRKQl3TAzWV84E6Yzf2QcJ-Cvday66VfRpdTu-PFizE82ZafsKTMxvEPQH4j8N_S-7Uwxu06fqYuSWpkxyRtc5CdPGS_Fik9UbctQOf9IjOw7z6_30XxsthsVpjDwP4RHwZ2IIPfIDsBFVdNZe6q86bHaK8090Q5CfWahqJJlsAokBrUtRgMag-NmIwUgCGrMc5h6NjeWDx68mN5_1EYpXMAQwlbuYk0ss3Ca9kE8UOHp25BNgPaoO4j_tU78_E4MN_iWivK3x6kAPmcMHCHaQHDCTv_-SOS5hVCDQfywCe-hOjKkQQj44mRSYvp5gPJhRyB1ixcZiZCKCoyn6E2uTly-j3FblPWX1IsuMr0RCPL8cS7u8_dwNeP7-LiMwTqUAMXfc4MTB73ewKq6Bt5A9zibtZ7o2hCJI3g4B8oRDfOGMkCDKBS_9t9EiZmIV8A2eGvMiHxKz6ZaekJHac5hxSziCBe-YRNaRxcL0KsFYLlyz9D7ye9H2FcpqcDYsnlsVzAnC74t2Tm6LXVKAMcr821ba2swBkWl3FC_gZmHq4VOdzvCR8buBjilg4_XejZCHp1hXGUW4djeftO0VI1pcn55P3wTjVEH8IbkRRtMrCKtJsqyKUQJEO4Hj98CBE0RnbqmO8PinPzHsUeYcPWgMqL8POkWJYF63XtH87QA9U6QflP6Vz4n_PPoCVe8w7clCgS7t6JrcG4xARIhQxvdNTM_bylWA=","summary":[{"type":"summary_text","text":"**Analyzing delayed completion after poison**"},{"type":"summary_text","text":"**Investigating client-side receiver thread failure**"}]},{"id":"fc_0ceecce325b7fe2d016a59750f84f08198b34c55950395c879","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading brain reader and deadlines\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"200-330\"}","call_id":"call_efCTTFSpiK2LhfXv3yjFrHfX","name":"read"},{"id":"fc_0ceecce325b7fe2d016a59750f85008198886eeeea17f0b256","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading brain event blocking\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"830-930\"}","call_id":"call_9JNAfTp6onQjC7by8QqHcGxp","name":"read"},{"id":"fc_0ceecce325b7fe2d016a59750f850c81988e6804b871747db7","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating dispatch attach serve\",\"pattern\":\"StreamFamily::Attach|serve_attach|AttachServeOutcome|finished\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"460-620\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_KVFdamhW76ElYpC5YcPhkQZA","name":"grep"},{"id":"fc_0ceecce325b7fe2d016a59750f851c819886270d56c98959bd","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking poison lifecycle tests\",\"pattern\":\"DISPATCH|poison|subscriber|stream-sub\",\"path\":\"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/brain_decouple.rs\",\"selector\":null,\"case\":false,\"gitignore\":true,\"skip\":null}","call_id":"call_aBEdVKShOml9hLFDTyhxLmNp","name":"grep"}]},"duration":9420.773800000548,"ttft":1204.1283999979496,"contextSnapshot":{"promptTokens":63480,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_efCTTFSpiK2LhfXv3yjFrHfX|fc_0ceecce325b7fe2d016a59750f84f08198b34c55950395c879","toolName":"read","startedAt":"2026-07-17T00:19:27.867Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Reading brain reader and deadlines"},"id":"a05ca0c3","parentId":"1b70c5a1","timestamp":"2026-07-17T00:19:27.867Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_9JNAfTp6onQjC7by8QqHcGxp|fc_0ceecce325b7fe2d016a59750f85008198886eeeea17f0b256","toolName":"read","startedAt":"2026-07-17T00:19:27.867Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Reading brain event blocking"},"id":"2a9603d0","parentId":"a05ca0c3","timestamp":"2026-07-17T00:19:27.867Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_KVFdamhW76ElYpC5YcPhkQZA|fc_0ceecce325b7fe2d016a59750f850c81988e6804b871747db7","toolName":"grep","startedAt":"2026-07-17T00:19:27.867Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Locating dispatch attach serve"},"id":"c9aa862e","parentId":"2a9603d0","timestamp":"2026-07-17T00:19:27.867Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_aBEdVKShOml9hLFDTyhxLmNp|fc_0ceecce325b7fe2d016a59750f851c819886270d56c98959bd","toolName":"grep","startedAt":"2026-07-17T00:19:27.867Z","args":{"path":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs; crates/spt-daemon/tests/redispatch.rs; crates/spt-daemon/tests/brain_decouple.rs"},"intent":"Checking poison lifecycle tests"},"id":"73fd756f","parentId":"c9aa862e","timestamp":"2026-07-17T00:19:27.867Z"}
{"type":"message","id":"629a6fd6","parentId":"73fd756f","timestamp":"2026-07-17T00:19:27.869Z","message":{"role":"toolResult","toolCallId":"call_KVFdamhW76ElYpC5YcPhkQZA|fc_0ceecce325b7fe2d016a59750f850c81988e6804b871747db7","toolName":"grep","content":[{"type":"text","text":" 462|    floor_seq: u64,\n*463|    finished: bool,\n 464|    paths: &DispatchPaths,\n 465|    registry: &RegistryHost,\n 466|) -> DispatchOutcome {\n...\n 478|\n*479|    // Gate round 1 (retire-on-sight, KNOWN-HAZARDS 7.41): a finished ATTACH\n 480|    // row's exchange is over — the operator detached — and replaying it would\n 481|    // take the live controller (pre-existing backlog + brain-down-detach\n 482|    // orderings, which the Served-report path can never see). Report Served\n...\n 485|    // through — their FIN is protocol, not completion (see\n*486|    // `finished_row_is_terminal`).\n*487|    if finished_row_is_terminal(family, finished) {\n*488|        return DispatchOutcome::Served(\"finished attach: terminal, retired unserved\".into());\n 489|    }\n 490|\n 491|    let mut brain = match connect(broker_name) {\n...\n 526|        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n*527|        StreamFamily::Attach => {\n 528|            // The gate subject is the session's OWNING endpoint, resolved\n 529|            // from the broker's table (the spawn's label) — never from wire\n 530|            // bytes. An unlabeled or unknown session refuses, fail-closed.\n...\n 543|            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n*544|            // before serve_attach subscribes — never mid-serve, which would swallow\n 545|            // interleaved attach records) and `None` on the local path.\n 546|            let (endpoint, remote_session) = match line\n 547|                .get(\"endpoint_id\")\n...\n 577|                // [impl->REQ-STREAM-OPENER-DURABLE]\n*578|                Some(endpoint) => match serve_attach(\n 579|                    &mut brain,\n 580|                    broker_name,\n 581|                    stream_id,"}],"details":{"scopePath":"crates/spt-daemon/src/dispatch.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/dispatch.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":8,"fileCount":1,"files":["crates/spt-daemon/src/dispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":8}],"truncated":false,"displayContent":" 462│    floor_seq: u64,\n*463│    finished: bool,\n 464│    paths: &DispatchPaths,\n 465│    registry: &RegistryHost,\n 466│) -> DispatchOutcome {\n    │...\n 478│\n*479│    // Gate round 1 (retire-on-sight, KNOWN-HAZARDS 7.41): a finished ATTACH\n 480│    // row's exchange is over — the operator detached — and replaying it would\n 481│    // take the live controller (pre-existing backlog + brain-down-detach\n 482│    // orderings, which the Served-report path can never see). Report Served\n    │...\n 485│    // through — their FIN is protocol, not completion (see\n*486│    // `finished_row_is_terminal`).\n*487│    if finished_row_is_terminal(family, finished) {\n*488│        return DispatchOutcome::Served(\"finished attach: terminal, retired unserved\".into());\n 489│    }\n 490│\n 491│    let mut brain = match connect(broker_name) {\n    │...\n 526│        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),\n*527│        StreamFamily::Attach => {\n 528│            // The gate subject is the session's OWNING endpoint, resolved\n 529│            // from the broker's table (the spawn's label) — never from wire\n 530│            // bytes. An unlabeled or unknown session refuses, fail-closed.\n    │...\n 543│            // dispatcher-resolved session id on the cross-node leg (resolved HERE,\n*544│            // before serve_attach subscribes — never mid-serve, which would swallow\n 545│            // interleaved attach records) and `None` on the local path.\n 546│            let (endpoint, remote_session) = match line\n 547│                .get(\"endpoint_id\")\n    │...\n 577│                // [impl->REQ-STREAM-OPENER-DURABLE]\n*578│                Some(endpoint) => match serve_attach(\n 579│                    &mut brain,\n 580│                    broker_name,\n 581│                    stream_id,"},"isError":false,"timestamp":1784247567869}}
{"type":"message","id":"a293cd14","parentId":"629a6fd6","timestamp":"2026-07-17T00:19:27.897Z","message":{"role":"toolResult","toolCallId":"call_9JNAfTp6onQjC7by8QqHcGxp|fc_0ceecce325b7fe2d016a59750f85008198886eeeea17f0b256","toolName":"read","content":[{"type":"text","text":"    pub fn read_event_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n        loop {\n…\n            match env.kind.as_str() {\n…\n                KIND_NET_STREAM_DATA => {\n…\n                    let bytes = decode_bytes(&ev.data_b64)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetStreamData {\n                        stream_id: ev.stream_id,\n                        seq: ev.seq,\n                        bytes,\n                    });\n                }\n                KIND_NET_STREAM_EOF => {\n                    let ev: NetStreamEof = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::NetStreamEof {\n                        stream_id: ev.stream_id,\n                        next_seq: ev.next_seq,\n                    });\n                }\n                KIND_NET_PRESENCE_EVENT => {\n                    let ev: NetPresenceEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    // Same exactly-once discipline as stream reads (D4c):\n                    // accept the contiguous next seq, drop a replay duplicate,\n                    // reject a forward gap (a lost liveness event must not\n                    // pass silently — a phantom \"still connected\" peer would).\n                    // [impl->REQ-EP-4]\n                    let cursor = self.presence_cursor.get_or_insert(ev.seq);\n                    if ev.seq < *cursor {\n                        continue; // replay duplicate — dedup\n                    }\n                    if ev.seq > *cursor {\n                        return Err(io::Error::new(\n                            io::ErrorKind::InvalidData,\n                            format!(\"presence gap: got seq {} want {}\", ev.seq, *cursor),\n                        ));\n                    }\n                    *cursor = ev.seq + 1;\n                    return Ok(BrokerEvent::NetPresence(ev));\n                }\n                KIND_ERROR => {\n                    let ev: ErrorEvent = serde_json::from_value(env.payload)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\n                    return Ok(BrokerEvent::Error {\n                        message: ev.message,\n                    });\n                }\n                _ => return Ok(BrokerEvent::Other(env)),\n            }\n        }\n    }\n\n    /// Like [`Self::read_event_until`] but for a PEER-REPLY read (a pump leg\n    /// waiting on a peer's forwarded stream response). A no-progress timeout is\n    /// the peer being connect-then-silent / half-alive — an ORDINARY per-peer\n    /// failure, so it is reclassified OUT of `TimedOut` and the pump's\n    /// `peer_outcome` drops ONLY that peer instead of POISONING the whole round +\n    /// supervised-restarting. The `TimedOut` poison stays RESERVED for a genuine\n    /// broker-IPC-CARRIER desync (the carrier ops — `net_open_stream` /\n    /// `net_stream_subscribe` / `net_stream_send` — keep their raw `TimedOut`).\n    /// Abandoning the peer's stream is safe: the exactly-once seq cursor stays\n    /// contiguous, and a late reply on the dropped stream id matches nothing (the\n    /// leg loops treat it as noise) — the desync the old blanket-poison feared\n    /// cannot happen on the still-ordered brain-IPC channel.\n    // [impl->REQ-PUMP-DIAL-FASTFAIL]\n    pub fn read_peer_reply_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n        self.read_event_until(deadline)\n            .map_err(reclassify_peer_reply_err)\n    }\n\n    /// Attach this (freshly-connected) brain to an **existing** broker session's\n    /// output from `from_seq`, e.g. a dedicated digest-feed subscriber that did\n    /// not spawn the session itself. Sets the resume cursor + session, then\n    /// subscribes (the broker replays from `from_seq`, then streams live). Unlike\n    /// [`Brain::handoff`] this does not touch the generation — it is a *second*\n    /// reader of a session another brain drives, not a logic handoff.\n    pub fn attach(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {\n        self.session_id = Some(session_id);\n        self.next_seq = from_seq;\n        // #6 (ADR-0031): a COLD attach receives a clean-screen repaint at pseudo-seq\n        // `broker.next_seq - 1`, not seq 0 — baseline the strict cursor on it.\n        self.baseline_next_output = from_seq == 0;\n        // A resume-mode brain (non-empty map) routes this session's output through\n        // the per-session map via `read_event`'s first-sight `or_insert` seed\n        // (D4-2b); a legacy brain (empty map) uses the `next_seq` set above. The\n        // resume-mode dedup-cursor reset for an explicit re-subscribe lives in\n        // `subscribe_with` (P1c, shared by `attach`/`attach_as`).\n        self.subscribe(session_id, from_seq)\n    }\n\n    /// Attach under the W2.5 controller/viewer model (REQ-RCVIEW-1): send a\n    /// role-aware subscribe with `intent` + the operator identity `by` (the\n    /// handshake-proven origin for a remote attach). The broker resolves the role\n    /// and replies a [`BrokerEvent::Subscribed`] the caller (a [`serve_attach`]\n    /// loop) reads to learn whether it became controller / viewer / was refused.\n    /// Like [`Brain::attach`] this is a second reader, not a logic handoff (the\n    /// generation is untouched).\n    ///\n    /// [`serve_attach`]: crate::attach::serve_attach\n    pub fn attach_as(\n        &mut self,\n        session_id: u64,\n        from_seq: u64,\n        intent: AttachIntent,\n        by: Option<&str>,\n    ) -> io::Result<()> {\n        self.session_id = Some(session_id);\n        self.next_seq = from_seq;\n        // #6 (ADR-0031): a COLD attach (`from_seq == 0` — the `rc` / `endpoint run\n…\n    }\n\n[Showing lines 829-933 of 2236. Use :934 to continue]"}],"details":{"truncation":{"content":"                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return Ok(BrokerEvent::NetStreamData {\r\n                        stream_id: ev.stream_id,\r\n                        seq: ev.seq,\r\n                        bytes,\r\n                    });\r\n                }\r\n                KIND_NET_STREAM_EOF => {\r\n                    let ev: NetStreamEof = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return Ok(BrokerEvent::NetStreamEof {\r\n                        stream_id: ev.stream_id,\r\n                        next_seq: ev.next_seq,\r\n                    });\r\n                }\r\n                KIND_NET_PRESENCE_EVENT => {\r\n                    let ev: NetPresenceEvent = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    // Same exactly-once discipline as stream reads (D4c):\r\n                    // accept the contiguous next seq, drop a replay duplicate,\r\n                    // reject a forward gap (a lost liveness event must not\r\n                    // pass silently — a phantom \"still connected\" peer would).\r\n                    // [impl->REQ-EP-4]\r\n                    let cursor = self.presence_cursor.get_or_insert(ev.seq);\r\n                    if ev.seq < *cursor {\r\n                        continue; // replay duplicate — dedup\r\n                    }\r\n                    if ev.seq > *cursor {\r\n                        return Err(io::Error::new(\r\n                            io::ErrorKind::InvalidData,\r\n                            format!(\"presence gap: got seq {} want {}\", ev.seq, *cursor),\r\n                        ));\r\n                    }\r\n                    *cursor = ev.seq + 1;\r\n                    return Ok(BrokerEvent::NetPresence(ev));\r\n                }\r\n                KIND_ERROR => {\r\n                    let ev: ErrorEvent = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return Ok(BrokerEvent::Error {\r\n                        message: ev.message,\r\n                    });\r\n                }\r\n                _ => return Ok(BrokerEvent::Other(env)),\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Like [`Self::read_event_until`] but for a PEER-REPLY read (a pump leg\r\n    /// waiting on a peer's forwarded stream response). A no-progress timeout is\r\n    /// the peer being connect-then-silent / half-alive — an ORDINARY per-peer\r\n    /// failure, so it is reclassified OUT of `TimedOut` and the pump's\r\n    /// `peer_outcome` drops ONLY that peer instead of POISONING the whole round +\r\n    /// supervised-restarting. The `TimedOut` poison stays RESERVED for a genuine\r\n    /// broker-IPC-CARRIER desync (the carrier ops — `net_open_stream` /\r\n    /// `net_stream_subscribe` / `net_stream_send` — keep their raw `TimedOut`).\r\n    /// Abandoning the peer's stream is safe: the exactly-once seq cursor stays\r\n    /// contiguous, and a late reply on the dropped stream id matches nothing (the\r\n    /// leg loops treat it as noise) — the desync the old blanket-poison feared\r\n    /// cannot happen on the still-ordered brain-IPC channel.\r\n    // [impl->REQ-PUMP-DIAL-FASTFAIL]\r\n    pub fn read_peer_reply_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\r\n        self.read_event_until(deadline)\r\n            .map_err(reclassify_peer_reply_err)\r\n    }\r\n\r\n    /// Attach this (freshly-connected) brain to an **existing** broker session's\r\n    /// output from `from_seq`, e.g. a dedicated digest-feed subscriber that did\r\n    /// not spawn the session itself. Sets the resume cursor + session, then\r\n    /// subscribes (the broker replays from `from_seq`, then streams live). Unlike\r\n    /// [`Brain::handoff`] this does not touch the generation — it is a *second*\r\n    /// reader of a session another brain drives, not a logic handoff.\r\n    pub fn attach(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {\r\n        self.session_id = Some(session_id);\r\n        self.next_seq = from_seq;\r\n        // #6 (ADR-0031): a COLD attach receives a clean-screen repaint at pseudo-seq\r\n        // `broker.next_seq - 1`, not seq 0 — baseline the strict cursor on it.\r\n        self.baseline_next_output = from_seq == 0;\r\n        // A resume-mode brain (non-empty map) routes this session's output through\r\n        // the per-session map via `read_event`'s first-sight `or_insert` seed\r\n        // (D4-2b); a legacy brain (empty map) uses the `next_seq` set above. The\r\n        // resume-mode dedup-cursor reset for an explicit re-subscribe lives in\r\n        // `subscribe_with` (P1c, shared by `attach`/`attach_as`).\r\n        self.subscribe(session_id, from_seq)\r\n    }\r\n\r\n    /// Attach under the W2.5 controller/viewer model (REQ-RCVIEW-1): send a\r\n    /// role-aware subscribe with `intent` + the operator identity `by` (the\r\n    /// handshake-proven origin for a remote attach). The broker resolves the role\r\n    /// and replies a [`BrokerEvent::Subscribed`] the caller (a [`serve_attach`]\r\n    /// loop) reads to learn whether it became controller / viewer / was refused.\r\n    /// Like [`Brain::attach`] this is a second reader, not a logic handoff (the\r\n    /// generation is untouched).\r\n    ///\r\n    /// [`serve_attach`]: crate::attach::serve_attach\r\n    pub fn attach_as(\r\n        &mut self,\r\n        session_id: u64,\r\n        from_seq: u64,\r\n        intent: AttachIntent,\r\n        by: Option<&str>,\r\n    ) -> io::Result<()> {\r\n        self.session_id = Some(session_id);\r\n        self.next_seq = from_seq;\r\n        // #6 (ADR-0031): a COLD attach (`from_seq == 0` — the `rc` / `endpoint run\r","truncated":true,"truncatedBy":"lines","totalLines":1408,"totalBytes":5799,"outputLines":105,"outputBytes":5799,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub fn read_event_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n        loop {\n…\n            match env.kind.as_str() {\n…\n                KIND_NET_STREAM_DATA => {\n…\n                    let bytes = decode_bytes(&ev.data_b64)\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return Ok(BrokerEvent::NetStreamData {\r\n                        stream_id: ev.stream_id,\r\n                        seq: ev.seq,\r\n                        bytes,\r\n                    });\r\n                }\r\n                KIND_NET_STREAM_EOF => {\r\n                    let ev: NetStreamEof = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return Ok(BrokerEvent::NetStreamEof {\r\n                        stream_id: ev.stream_id,\r\n                        next_seq: ev.next_seq,\r\n                    });\r\n                }\r\n                KIND_NET_PRESENCE_EVENT => {\r\n                    let ev: NetPresenceEvent = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    // Same exactly-once discipline as stream reads (D4c):\r\n                    // accept the contiguous next seq, drop a replay duplicate,\r\n                    // reject a forward gap (a lost liveness event must not\r\n                    // pass silently — a phantom \"still connected\" peer would).\r\n                    // [impl->REQ-EP-4]\r\n                    let cursor = self.presence_cursor.get_or_insert(ev.seq);\r\n                    if ev.seq < *cursor {\r\n                        continue; // replay duplicate — dedup\r\n                    }\r\n                    if ev.seq > *cursor {\r\n                        return Err(io::Error::new(\r\n                            io::ErrorKind::InvalidData,\r\n                            format!(\"presence gap: got seq {} want {}\", ev.seq, *cursor),\r\n                        ));\r\n                    }\r\n                    *cursor = ev.seq + 1;\r\n                    return Ok(BrokerEvent::NetPresence(ev));\r\n                }\r\n                KIND_ERROR => {\r\n                    let ev: ErrorEvent = serde_json::from_value(env.payload)\r\n                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;\r\n                    return Ok(BrokerEvent::Error {\r\n                        message: ev.message,\r\n                    });\r\n                }\r\n                _ => return Ok(BrokerEvent::Other(env)),\r\n            }\r\n        }\r\n    }\r\n\r\n    /// Like [`Self::read_event_until`] but for a PEER-REPLY read (a pump leg\r\n    /// waiting on a peer's forwarded stream response). A no-progress timeout is\r\n    /// the peer being connect-then-silent / half-alive — an ORDINARY per-peer\r\n    /// failure, so it is reclassified OUT of `TimedOut` and the pump's\r\n    /// `peer_outcome` drops ONLY that peer instead of POISONING the whole round +\r\n    /// supervised-restarting. The `TimedOut` poison stays RESERVED for a genuine\r\n    /// broker-IPC-CARRIER desync (the carrier ops — `net_open_stream` /\r\n    /// `net_stream_subscribe` / `net_stream_send` — keep their raw `TimedOut`).\r\n    /// Abandoning the peer's stream is safe: the exactly-once seq cursor stays\r\n    /// contiguous, and a late reply on the dropped stream id matches nothing (the\r\n    /// leg loops treat it as noise) — the desync the old blanket-poison feared\r\n    /// cannot happen on the still-ordered brain-IPC channel.\r\n    // [impl->REQ-PUMP-DIAL-FASTFAIL]\r\n    pub fn read_peer_reply_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\r\n        self.read_event_until(deadline)\r\n            .map_err(reclassify_peer_reply_err)\r\n    }\r\n\r\n    /// Attach this (freshly-connected) brain to an **existing** broker session's\r\n    /// output from `from_seq`, e.g. a dedicated digest-feed subscriber that did\r\n    /// not spawn the session itself. Sets the resume cursor + session, then\r\n    /// subscribes (the broker replays from `from_seq`, then streams live). Unlike\r\n    /// [`Brain::handoff`] this does not touch the generation — it is a *second*\r\n    /// reader of a session another brain drives, not a logic handoff.\r\n    pub fn attach(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {\r\n        self.session_id = Some(session_id);\r\n        self.next_seq = from_seq;\r\n        // #6 (ADR-0031): a COLD attach receives a clean-screen repaint at pseudo-seq\r\n        // `broker.next_seq - 1`, not seq 0 — baseline the strict cursor on it.\r\n        self.baseline_next_output = from_seq == 0;\r\n        // A resume-mode brain (non-empty map) routes this session's output through\r\n        // the per-session map via `read_event`'s first-sight `or_insert` seed\r\n        // (D4-2b); a legacy brain (empty map) uses the `next_seq` set above. The\r\n        // resume-mode dedup-cursor reset for an explicit re-subscribe lives in\r\n        // `subscribe_with` (P1c, shared by `attach`/`attach_as`).\r\n        self.subscribe(session_id, from_seq)\r\n    }\r\n\r\n    /// Attach under the W2.5 controller/viewer model (REQ-RCVIEW-1): send a\r\n    /// role-aware subscribe with `intent` + the operator identity `by` (the\r\n    /// handshake-proven origin for a remote attach). The broker resolves the role\r\n    /// and replies a [`BrokerEvent::Subscribed`] the caller (a [`serve_attach`]\r\n    /// loop) reads to learn whether it became controller / viewer / was refused.\r\n    /// Like [`Brain::attach`] this is a second reader, not a logic handoff (the\r\n    /// generation is untouched).\r\n    ///\r\n    /// [`serve_attach`]: crate::attach::serve_attach\r\n    pub fn attach_as(\r\n        &mut self,\r\n        session_id: u64,\r\n        from_seq: u64,\r\n        intent: AttachIntent,\r\n        by: Option<&str>,\r\n    ) -> io::Result<()> {\r\n        self.session_id = Some(session_id);\r\n        self.next_seq = from_seq;\r\n        // #6 (ADR-0031): a COLD attach (`from_seq == 0` — the `rc` / `endpoint run\r\n…\n    }","startLine":636,"lineNumbers":[636,637,null,639,null,808,null,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,880,881,882,883,884,885,886,887,888,889,890,891,892,893,894,895,896,897,898,899,900,901,902,903,904,905,906,907,908,909,910,911,912,913,914,915,916,917,918,919,920,921,922,923,924,925,926,927,928,929,930,931,932,933,null,940]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2236,"totalBytes":5799,"outputLines":105,"outputBytes":5799,"shownRange":{"start":829,"end":933},"nextOffset":934}}},"isError":false,"timestamp":1784247567896}}
{"type":"message","id":"3589e56d","parentId":"a293cd14","timestamp":"2026-07-17T00:19:27.897Z","message":{"role":"toolResult","toolCallId":"call_efCTTFSpiK2LhfXv3yjFrHfX|fc_0ceecce325b7fe2d016a59750f84f08198b34c55950395c879","toolName":"read","content":[{"type":"text","text":"/// portable read timeout (`set_recv_timeout` → `no_timeouts()`; `set_nonblocking`\n/// → deprecated `PIPE_NOWAIT`, which corrupts mid-stream — proven by the mesh\n/// E2E). So the pump carrier instead **splits** the stream: a dedicated reader\n/// thread does blocking [`read_frame`] on the [`RecvHalf`] and forwards each\n/// framed result down a channel; the main thread writes on the [`SendHalf`] and\n/// reads with [`Receiver::recv_timeout`]. Blocking reads work on both OSes; the\n/// channel supplies the deadline. On a timeout the pump bubbles out to a\n/// supervised restart (a fresh [`Brain`] → fresh split → fresh reader thread);\n/// the old reader, parked in `read_frame`, is abandoned (KH 7.6 leak watch — the\n/// old conn's drop eventually errors its read and the thread exits).\nenum BrainConn {\n    /// The ordinary blocking carrier — every non-pump brain.\n    Whole(Stream),\n    /// The pump carrier: writes go out [`SendHalf`]; framed reads arrive over\n    /// `frames` from the reader thread (`_reader`, named `pump-ipc-reader`).\n    Split {\n        send: SendHalf,\n        frames: Receiver<io::Result<Envelope>>,\n        /// The reader-thread handle — held so the thread is owned, not detached\n        /// silently. On a supervised restart the whole [`Brain`] drops; this\n        /// handle drops with it (the thread, blocked in `read_frame`, is\n        /// abandoned and exits when the old conn closes — KH 7.6).\n        _reader: JoinHandle<()>,\n    },\n}\n\nimpl BrainConn {\n    /// Split a connected+handshaked stream into the pump carrier: spawn the\n    /// `pump-ipc-reader` thread (blocking [`read_frame`] → channel) and keep the\n    /// [`SendHalf`] for writes. The reader logs spawn/exit so a leaked (parked)\n    /// reader is diagnosable in the field (KH 7.6).\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn split_with_reader(stream: Stream) -> BrainConn {\n        let (mut recv, send) = stream.split();\n        let (tx, frames) = mpsc::channel();\n        let reader = thread::Builder::new()\n            .name(\"pump-ipc-reader\".to_string())\n            .spawn(move || {\n                eprintln!(\"PUMP_IPC_READER: spawned\");\n                loop {\n                    let framed = read_frame(&mut recv);\n                    let was_err = framed.is_err();\n                    // Receiver gone (Brain dropped) → stop reading.\n                    if tx.send(framed).is_err() {\n                        break;\n                    }\n                    // A transport error (conn closed / hung up) is terminal —\n                    // forward it once (above), then exit so the thread never\n                    // spins on a dead half.\n                    if was_err {\n                        break;\n                    }\n                }\n                eprintln!(\"PUMP_IPC_READER: exited\");\n            })\n            .expect(\"spawn pump-ipc-reader thread\");\n        BrainConn::Split {\n            send,\n            frames,\n            _reader: reader,\n        }\n    }\n}\n\n/// A live brain attached to a broker.\npub struct Brain {\n    conn: BrainConn,\n    generation: u64,\n    gen_start_ms: u64,\n    session_id: Option<u64>,\n    /// The next output sequence still needed (resume cursor / dedup floor).\n    next_seq: u64,\n    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\n    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// Durability across a handoff is the logic layer's job (a re-attaching\n    /// brain queries `net-streams` and re-subscribes from its durable cursors).\n    net_cursors: std::collections::HashMap<u64, u64>,\n    /// The presence resume cursor (D4c) — the conn-liveness sibling of\n    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\n    /// `None` until subscribed (or the first event arrives).\n    presence_cursor: Option<u64>,\n    /// Per-session **resume** output cursors (restoration D4-2): the multi-session\n    /// continuity state a cold-started brain rebuilds from the broker via\n    /// [`Brain::resume_sessions`], replacing the retired single-session\n    /// `BrainState` handoff. A session present here is in **resume mode** — its\n    /// `KIND_OUTPUT` dedup is **dedup-below + snap-above, NO reject-gap** (seeded\n    /// at the broker's `resume_seq`): drop `seq < cursor` (the at-least-once\n    /// boundary re-send) and accept-and-snap `seq >= cursor` (the contiguous case\n    /// *and* a post-eviction ring-floor jump — the broker's `Mutex`-held replay\n    /// cannot reorder, so a forward jump is only ever a legitimate eviction clamp,\n    /// never a real gap). A session **absent** here uses the strict single-session\n    /// `next_seq` path (reject-gap) byte-for-byte — the legacy spawn/drive seat.\n    session_cursors: std::collections::HashMap<u64, u64>,\n    /// When `Some`, every reply read is bounded by this per-call deadline budget\n    /// — set only by [`Brain::cold_start_pump`], which also splits the carrier\n    /// into the reader-thread form so the deadline can be honored over a channel\n    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\n    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\n    /// single-threaded pump, where a black-holed peer would wedge the whole loop\n    /// (KNOWN-HAZARDS — a blocked brain-IPC read must never wedge the pump). The\n    /// two facts are coupled at construction: `Some` ⇔ [`BrainConn::Split`].\n    io_timeout: Option<Duration>,\n    /// #6 (ADR-0031): a COLD attach (`from_seq == 0`, set by [`Brain::attach`] /\n    /// [`Brain::attach_as`]) receives a synthesized clean-screen REPAINT as its\n    /// first `KIND_OUTPUT` frame, at pseudo-seq `broker.next_seq - 1` — NOT seq 0.\n    /// When `true`, the strict legacy `next_seq` path adopts that first frame's seq\n    /// as the baseline (accept-and-seed) instead of reject-gapping it, then reverts\n    /// to strict reject-gap for every subsequent LIVE frame (so a b4 drop still\n    /// re-fetches — B2 live exactly-once preserved). Only ever consulted on the\n    /// legacy single-session path; a resume-mode brain (snap-above) ignores it. A\n    /// resume-from-floor re-fetch (`from_seq > 0`) leaves this `false` → raw-ring\n    /// replay + strict reject-gap, entirely unchanged.\n    baseline_next_output: bool,\n}\n\nimpl Brain {\n    /// Cold start: connect, handshake, fresh generation 0, `gen_start = now`.\n    /// The ordinary blocking carrier ([`BrainConn::Whole`]) — every client\n    /// except the peer pump, which uses [`Brain::cold_start_pump`].\n    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {\n        let mut conn = LocalSocketTransport::connect(name)?;\n        send_hello(&mut conn, Role::Brain)?;\n        Ok(Brain {\n            conn: BrainConn::Whole(conn),\n            generation: 0,\n            gen_start_ms: now_ms,\n            session_id: None,\n            next_seq: 0,\n            net_cursors: std::collections::HashMap::new(),\n            presence_cursor: None,\n            session_cursors: std::collections::HashMap::new(),\n            io_timeout: None,\n            baseline_next_output: false,\n        })\n    }\n…\n}\n\n[Showing lines 199-333 of 2236. Use :334 to continue]"}],"details":{"truncation":{"content":"/// portable read timeout (`set_recv_timeout` → `no_timeouts()`; `set_nonblocking`\r\n/// → deprecated `PIPE_NOWAIT`, which corrupts mid-stream — proven by the mesh\r\n/// E2E). So the pump carrier instead **splits** the stream: a dedicated reader\r\n/// thread does blocking [`read_frame`] on the [`RecvHalf`] and forwards each\r\n/// framed result down a channel; the main thread writes on the [`SendHalf`] and\r\n/// reads with [`Receiver::recv_timeout`]. Blocking reads work on both OSes; the\r\n/// channel supplies the deadline. On a timeout the pump bubbles out to a\r\n/// supervised restart (a fresh [`Brain`] → fresh split → fresh reader thread);\r\n/// the old reader, parked in `read_frame`, is abandoned (KH 7.6 leak watch — the\r\n/// old conn's drop eventually errors its read and the thread exits).\r\nenum BrainConn {\r\n    /// The ordinary blocking carrier — every non-pump brain.\r\n    Whole(Stream),\r\n    /// The pump carrier: writes go out [`SendHalf`]; framed reads arrive over\r\n    /// `frames` from the reader thread (`_reader`, named `pump-ipc-reader`).\r\n    Split {\r\n        send: SendHalf,\r\n        frames: Receiver<io::Result<Envelope>>,\r\n        /// The reader-thread handle — held so the thread is owned, not detached\r\n        /// silently. On a supervised restart the whole [`Brain`] drops; this\r\n        /// handle drops with it (the thread, blocked in `read_frame`, is\r\n        /// abandoned and exits when the old conn closes — KH 7.6).\r\n        _reader: JoinHandle<()>,\r\n    },\r\n}\r\n\r\nimpl BrainConn {\r\n    /// Split a connected+handshaked stream into the pump carrier: spawn the\r\n    /// `pump-ipc-reader` thread (blocking [`read_frame`] → channel) and keep the\r\n    /// [`SendHalf`] for writes. The reader logs spawn/exit so a leaked (parked)\r\n    /// reader is diagnosable in the field (KH 7.6).\r\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n    fn split_with_reader(stream: Stream) -> BrainConn {\r\n        let (mut recv, send) = stream.split();\r\n        let (tx, frames) = mpsc::channel();\r\n        let reader = thread::Builder::new()\r\n            .name(\"pump-ipc-reader\".to_string())\r\n            .spawn(move || {\r\n                eprintln!(\"PUMP_IPC_READER: spawned\");\r\n                loop {\r\n                    let framed = read_frame(&mut recv);\r\n                    let was_err = framed.is_err();\r\n                    // Receiver gone (Brain dropped) → stop reading.\r\n                    if tx.send(framed).is_err() {\r\n                        break;\r\n                    }\r\n                    // A transport error (conn closed / hung up) is terminal —\r\n                    // forward it once (above), then exit so the thread never\r\n                    // spins on a dead half.\r\n                    if was_err {\r\n                        break;\r\n                    }\r\n                }\r\n                eprintln!(\"PUMP_IPC_READER: exited\");\r\n            })\r\n            .expect(\"spawn pump-ipc-reader thread\");\r\n        BrainConn::Split {\r\n            send,\r\n            frames,\r\n            _reader: reader,\r\n        }\r\n    }\r\n}\r\n\r\n/// A live brain attached to a broker.\r\npub struct Brain {\r\n    conn: BrainConn,\r\n    generation: u64,\r\n    gen_start_ms: u64,\r\n    session_id: Option<u64>,\r\n    /// The next output sequence still needed (resume cursor / dedup floor).\r\n    next_seq: u64,\r\n    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\r\n    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\r\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\r\n    /// Durability across a handoff is the logic layer's job (a re-attaching\r\n    /// brain queries `net-streams` and re-subscribes from its durable cursors).\r\n    net_cursors: std::collections::HashMap<u64, u64>,\r\n    /// The presence resume cursor (D4c) — the conn-liveness sibling of\r\n    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\r\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\r\n    /// `None` until subscribed (or the first event arrives).\r\n    presence_cursor: Option<u64>,\r\n    /// Per-session **resume** output cursors (restoration D4-2): the multi-session\r\n    /// continuity state a cold-started brain rebuilds from the broker via\r\n    /// [`Brain::resume_sessions`], replacing the retired single-session\r\n    /// `BrainState` handoff. A session present here is in **resume mode** — its\r\n    /// `KIND_OUTPUT` dedup is **dedup-below + snap-above, NO reject-gap** (seeded\r\n    /// at the broker's `resume_seq`): drop `seq < cursor` (the at-least-once\r\n    /// boundary re-send) and accept-and-snap `seq >= cursor` (the contiguous case\r\n    /// *and* a post-eviction ring-floor jump — the broker's `Mutex`-held replay\r\n    /// cannot reorder, so a forward jump is only ever a legitimate eviction clamp,\r\n    /// never a real gap). A session **absent** here uses the strict single-session\r\n    /// `next_seq` path (reject-gap) byte-for-byte — the legacy spawn/drive seat.\r\n    session_cursors: std::collections::HashMap<u64, u64>,\r\n    /// When `Some`, every reply read is bounded by this per-call deadline budget\r\n    /// — set only by [`Brain::cold_start_pump`], which also splits the carrier\r\n    /// into the reader-thread form so the deadline can be honored over a channel\r\n    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\r\n    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\r\n    /// single-threaded pump, where a black-holed peer would wedge the whole loop\r\n    /// (KNOWN-HAZARDS — a blocked brain-IPC read must never wedge the pump). The\r\n    /// two facts are coupled at construction: `Some` ⇔ [`BrainConn::Split`].\r\n    io_timeout: Option<Duration>,\r\n    /// #6 (ADR-0031): a COLD attach (`from_seq == 0`, set by [`Brain::attach`] /\r\n    /// [`Brain::attach_as`]) receives a synthesized clean-screen REPAINT as its\r\n    /// first `KIND_OUTPUT` frame, at pseudo-seq `broker.next_seq - 1` — NOT seq 0.\r\n    /// When `true`, the strict legacy `next_seq` path adopts that first frame's seq\r\n    /// as the baseline (accept-and-seed) instead of reject-gapping it, then reverts\r\n    /// to strict reject-gap for every subsequent LIVE frame (so a b4 drop still\r\n    /// re-fetches — B2 live exactly-once preserved). Only ever consulted on the\r\n    /// legacy single-session path; a resume-mode brain (snap-above) ignores it. A\r\n    /// resume-from-floor re-fetch (`from_seq > 0`) leaves this `false` → raw-ring\r\n    /// replay + strict reject-gap, entirely unchanged.\r\n    baseline_next_output: bool,\r\n}\r\n\r\nimpl Brain {\r\n    /// Cold start: connect, handshake, fresh generation 0, `gen_start = now`.\r\n    /// The ordinary blocking carrier ([`BrainConn::Whole`]) — every client\r\n    /// except the peer pump, which uses [`Brain::cold_start_pump`].\r\n    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {\r\n        let mut conn = LocalSocketTransport::connect(name)?;\r\n        send_hello(&mut conn, Role::Brain)?;\r\n        Ok(Brain {\r\n            conn: BrainConn::Whole(conn),\r\n            generation: 0,\r\n            gen_start_ms: now_ms,\r\n            session_id: None,\r\n            next_seq: 0,\r\n            net_cursors: std::collections::HashMap::new(),\r\n            presence_cursor: None,\r\n            session_cursors: std::collections::HashMap::new(),\r\n            io_timeout: None,\r\n            baseline_next_output: false,\r","truncated":true,"truncatedBy":"lines","totalLines":2038,"totalBytes":7455,"outputLines":135,"outputBytes":7455,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"/// portable read timeout (`set_recv_timeout` → `no_timeouts()`; `set_nonblocking`\r\n/// → deprecated `PIPE_NOWAIT`, which corrupts mid-stream — proven by the mesh\r\n/// E2E). So the pump carrier instead **splits** the stream: a dedicated reader\r\n/// thread does blocking [`read_frame`] on the [`RecvHalf`] and forwards each\r\n/// framed result down a channel; the main thread writes on the [`SendHalf`] and\r\n/// reads with [`Receiver::recv_timeout`]. Blocking reads work on both OSes; the\r\n/// channel supplies the deadline. On a timeout the pump bubbles out to a\r\n/// supervised restart (a fresh [`Brain`] → fresh split → fresh reader thread);\r\n/// the old reader, parked in `read_frame`, is abandoned (KH 7.6 leak watch — the\r\n/// old conn's drop eventually errors its read and the thread exits).\r\nenum BrainConn {\r\n    /// The ordinary blocking carrier — every non-pump brain.\r\n    Whole(Stream),\r\n    /// The pump carrier: writes go out [`SendHalf`]; framed reads arrive over\r\n    /// `frames` from the reader thread (`_reader`, named `pump-ipc-reader`).\r\n    Split {\r\n        send: SendHalf,\r\n        frames: Receiver<io::Result<Envelope>>,\r\n        /// The reader-thread handle — held so the thread is owned, not detached\r\n        /// silently. On a supervised restart the whole [`Brain`] drops; this\r\n        /// handle drops with it (the thread, blocked in `read_frame`, is\r\n        /// abandoned and exits when the old conn closes — KH 7.6).\r\n        _reader: JoinHandle<()>,\r\n    },\r\n}\r\n\r\nimpl BrainConn {\r\n    /// Split a connected+handshaked stream into the pump carrier: spawn the\r\n    /// `pump-ipc-reader` thread (blocking [`read_frame`] → channel) and keep the\r\n    /// [`SendHalf`] for writes. The reader logs spawn/exit so a leaked (parked)\r\n    /// reader is diagnosable in the field (KH 7.6).\r\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n    fn split_with_reader(stream: Stream) -> BrainConn {\r\n        let (mut recv, send) = stream.split();\r\n        let (tx, frames) = mpsc::channel();\r\n        let reader = thread::Builder::new()\r\n            .name(\"pump-ipc-reader\".to_string())\r\n            .spawn(move || {\r\n                eprintln!(\"PUMP_IPC_READER: spawned\");\r\n                loop {\r\n                    let framed = read_frame(&mut recv);\r\n                    let was_err = framed.is_err();\r\n                    // Receiver gone (Brain dropped) → stop reading.\r\n                    if tx.send(framed).is_err() {\r\n                        break;\r\n                    }\r\n                    // A transport error (conn closed / hung up) is terminal —\r\n                    // forward it once (above), then exit so the thread never\r\n                    // spins on a dead half.\r\n                    if was_err {\r\n                        break;\r\n                    }\r\n                }\r\n                eprintln!(\"PUMP_IPC_READER: exited\");\r\n            })\r\n            .expect(\"spawn pump-ipc-reader thread\");\r\n        BrainConn::Split {\r\n            send,\r\n            frames,\r\n            _reader: reader,\r\n        }\r\n    }\r\n}\r\n\r\n/// A live brain attached to a broker.\r\npub struct Brain {\r\n    conn: BrainConn,\r\n    generation: u64,\r\n    gen_start_ms: u64,\r\n    session_id: Option<u64>,\r\n    /// The next output sequence still needed (resume cursor / dedup floor).\r\n    next_seq: u64,\r\n    /// Per-net-stream resume cursors (D4b) — the stream-read siblings of\r\n    /// `next_seq`, set by [`Brain::net_stream_subscribe`] and advanced by\r\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\r\n    /// Durability across a handoff is the logic layer's job (a re-attaching\r\n    /// brain queries `net-streams` and re-subscribes from its durable cursors).\r\n    net_cursors: std::collections::HashMap<u64, u64>,\r\n    /// The presence resume cursor (D4c) — the conn-liveness sibling of\r\n    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by\r\n    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.\r\n    /// `None` until subscribed (or the first event arrives).\r\n    presence_cursor: Option<u64>,\r\n    /// Per-session **resume** output cursors (restoration D4-2): the multi-session\r\n    /// continuity state a cold-started brain rebuilds from the broker via\r\n    /// [`Brain::resume_sessions`], replacing the retired single-session\r\n    /// `BrainState` handoff. A session present here is in **resume mode** — its\r\n    /// `KIND_OUTPUT` dedup is **dedup-below + snap-above, NO reject-gap** (seeded\r\n    /// at the broker's `resume_seq`): drop `seq < cursor` (the at-least-once\r\n    /// boundary re-send) and accept-and-snap `seq >= cursor` (the contiguous case\r\n    /// *and* a post-eviction ring-floor jump — the broker's `Mutex`-held replay\r\n    /// cannot reorder, so a forward jump is only ever a legitimate eviction clamp,\r\n    /// never a real gap). A session **absent** here uses the strict single-session\r\n    /// `next_seq` path (reject-gap) byte-for-byte — the legacy spawn/drive seat.\r\n    session_cursors: std::collections::HashMap<u64, u64>,\r\n    /// When `Some`, every reply read is bounded by this per-call deadline budget\r\n    /// — set only by [`Brain::cold_start_pump`], which also splits the carrier\r\n    /// into the reader-thread form so the deadline can be honored over a channel\r\n    /// (`recv_timeout`). `None` (default) = the ordinary blocking client — a\r\n    /// brain-IPC read waits forever, which is correct everywhere EXCEPT the\r\n    /// single-threaded pump, where a black-holed peer would wedge the whole loop\r\n    /// (KNOWN-HAZARDS — a blocked brain-IPC read must never wedge the pump). The\r\n    /// two facts are coupled at construction: `Some` ⇔ [`BrainConn::Split`].\r\n    io_timeout: Option<Duration>,\r\n    /// #6 (ADR-0031): a COLD attach (`from_seq == 0`, set by [`Brain::attach`] /\r\n    /// [`Brain::attach_as`]) receives a synthesized clean-screen REPAINT as its\r\n    /// first `KIND_OUTPUT` frame, at pseudo-seq `broker.next_seq - 1` — NOT seq 0.\r\n    /// When `true`, the strict legacy `next_seq` path adopts that first frame's seq\r\n    /// as the baseline (accept-and-seed) instead of reject-gapping it, then reverts\r\n    /// to strict reject-gap for every subsequent LIVE frame (so a b4 drop still\r\n    /// re-fetches — B2 live exactly-once preserved). Only ever consulted on the\r\n    /// legacy single-session path; a resume-mode brain (snap-above) ignores it. A\r\n    /// resume-from-floor re-fetch (`from_seq > 0`) leaves this `false` → raw-ring\r\n    /// replay + strict reject-gap, entirely unchanged.\r\n    baseline_next_output: bool,\r\n}\r\n\r\nimpl Brain {\r\n    /// Cold start: connect, handshake, fresh generation 0, `gen_start = now`.\r\n    /// The ordinary blocking carrier ([`BrainConn::Whole`]) — every client\r\n    /// except the peer pump, which uses [`Brain::cold_start_pump`].\r\n    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {\r\n        let mut conn = LocalSocketTransport::connect(name)?;\r\n        send_hello(&mut conn, Role::Brain)?;\r\n        Ok(Brain {\r\n            conn: BrainConn::Whole(conn),\r\n            generation: 0,\r\n            gen_start_ms: now_ms,\r\n            session_id: None,\r\n            next_seq: 0,\r\n            net_cursors: std::collections::HashMap::new(),\r\n            presence_cursor: None,\r\n            session_cursors: std::collections::HashMap::new(),\r\n            io_timeout: None,\r\n            baseline_next_output: false,\r\n        })\n    }\n…\n}","startLine":199,"lineNumbers":[199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,null,1782]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2236,"totalBytes":7455,"outputLines":135,"outputBytes":7455,"shownRange":{"start":199,"end":333},"nextOffset":334}}},"isError":false,"timestamp":1784247567896}}
{"type":"message","id":"44e4f224","parentId":"3589e56d","timestamp":"2026-07-17T00:19:27.905Z","message":{"role":"toolResult","toolCallId":"call_aBEdVKShOml9hLFDTyhxLmNp|fc_0ceecce325b7fe2d016a59750f851c819886270d56c98959bd","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/tests/\n## conn_blackhole_lifecycle.rs\n 13|//! write timeout) and, against CURRENT v0.30.6 semantics (`crates/spt-daemon/\n*14|//! src/conn.rs` bounded/cancelable/poison-on-failure writes, landed fc048bb),\n 15|//! asserts hertz's FIVE invariants for ONE incident:\n 16|//!\n 17|//!   1. UNRELATED SESSIONS CONTINUE — a second, independent session's viewer keeps\n...\n 31|//!      session COMPLETES and RECEIVES replayed output from the frozen cursor\n*32|//!      (the fan-out accepts + serves a new subscriber after retirement);\n 33|//!   5. NO LOCK OR TASK REMAINS OWNED BY THE RETIRED CONNECTION — asserted via\n 34|//!      PROXIES (this invariant has no direct seam): after retirement the broker\n 35|//!      still answers a control-plane `KIND_SESSIONS` request PROMPTLY (no lock\n...\n 62|//! `set_timeout` → `Unsupported`) — the exact field failure. On Unix the\n*63|//! poison/retire path is a `shutdown(SHUT_RDWR)` abort of a buffer-fill block: a\n 64|//! genuinely DIFFERENT mechanism, not this incident. Faking a Unix companion\n 65|//! would test a different thing, so — mirroring the r4 gate's own\n 66|//! `#[cfg(windows)]` leg — the entire harness is Windows-only. On other targets\n...\n 90|    decode_bytes, encode_bytes, InputReq, OutputEvent, SessionsReply, SpawnReq, Spawned,\n*91|    StallEvictsReply, SubscribeOutcome, SubscribeReq, SubscribedReply, KIND_INPUT, KIND_OUTPUT,\n 92|    KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN, KIND_SPAWNED, KIND_STALL_EVICTS,\n 93|    KIND_STALL_EVICTS_REPLY, KIND_SUBSCRIBE, KIND_SUBSCRIBED,\n 94|};\n...\n 221|\n*222|/// One `KIND_SESSIONS` round-trip, returning the reply. The dispatch runs the\n 223|/// `reap_dead_controller` → time-based `stall_evict_controller`, so polling this\n 224|/// both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 225|/// Bounded thread + channel. Verbatim sibling of the r4 gate helper.\n...\n 276|            KIND_SUBSCRIBE,\n*277|            serde_json::to_value(SubscribeReq {\n 278|                session_id: sid_u,\n 279|                from_seq: 0,\n 280|                intent: AttachIntent::Viewer,\n...\n 312|            KIND_SUBSCRIBE,\n*313|            serde_json::to_value(SubscribeReq {\n 314|                session_id: sid_b,\n 315|                from_seq: 0,\n 316|                intent: AttachIntent::Control,\n...\n 437|    //    stall-evict). Record t_release + freeze the resume cursor for the sid_b\n*438|    //    row. Accept a tally of {1, 2}: POISON-BEATS-EVICT (F-039 rider,\n*439|    //    doyle-ruled) — the physical watchdog can poison first and record the same\n 440|    //    release class, so the tally can step 0→2 when watchdog races the reap. ──\n 441|    let mut stall_evicts_seen: u64 = 0;\n 442|    let mut t_release: Option<Instant> = None;\n...\n 502|    //    bound): a write on the OLD client Stream must FAIL — the physical conn\n*503|    //    was retired (poisoned + severed), not merely logically stall-evicted. ──\n 504|    let old_client_write: Option<std::io::Result<()>> = r_stream.as_mut().map(|s| {\n 505|        write_frame(s, &Envelope::new(KIND_SESSIONS, serde_json::Value::Null))\n 506|    });\n...\n 535|            KIND_SUBSCRIBE,\n*536|            serde_json::to_value(SubscribeReq {\n 537|                session_id: sid_b,\n 538|                from_seq: resume_seq,\n 539|                intent: AttachIntent::Viewer,\n...\n 623|        \"HARNESS: the one wedged controller's release must be tallied once (logical \\\n*624|         reap OR physical poison) or twice (both raced — poison-beats-evict, \\\n 625|         field-accepted v0.30.6); got {stall_evicts_seen}\"\n 626|    );\n 627|\n...\n 685|        \"INVARIANT 4: the fresh viewer must RECEIVE replayed output from the frozen \\\n*686|         cursor (resume_seq={resume_seq}) — the fan-out must serve a new subscriber \\\n 687|         after retirement; got fv_got_output={fv_got_output} fv_first_seq={fv_first_seq:?}\"\n 688|    );\n 689|\n## redispatch.rs\n*1|//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n 2|//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n*3|//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n*4|//! a dispatcher GENERATION whose death drops its workers' broker conns\n 5|//! exactly like a production brain death). NO manual re-serve anywhere: the\n 6|//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n 7|//! hand-stepped a known stream and bypassed exactly the rediscovery/\n...\n 10|//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\n*11|//!   endpoint/origin; restart the dispatcher generation only → the\n 12|//!   historical stream neither takes nor clears the current controller\n 13|//!   (post-restart input still renders; no EOF; no replay-storm log line;\n 14|//!   the retired row left the broker's enumeration).\n 15|//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n*16|//!   restart the dispatcher → durable classification (the pinned opener)\n 17|//!   resumes the SAME operator stream.\n 18|//! - D3: ONE injected transient worker-start failure → the claim requeues\n 19|//!   and the retry serves the stream (no permanent abandonment, no\n...\n 108|\n*109|/// One REAL dispatcher generation: the `dispatch_fixture` child running\n*110|/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n 111|/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n 112|struct Generation {\n 113|    child: Child,\n...\n 119|        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n*120|        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 121|        cmd.arg(broker_name)\n 122|            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\n 123|            .stdout(Stdio::null())\n...\n 127|        }\n*128|        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n 129|        Generation { child, log }\n 130|    }\n 131|\n...\n 211|\n*212|// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n 213|// through PRODUCTION rediscovery: a finished historical Attach and a current\n*214|// active Attach on the same endpoint/origin; only the dispatcher generation\n 215|// restarts. The historical stream must neither take nor clear the current\n 216|// controller: post-restart input still renders (the frozen-PTY symptom is\n 217|// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n...\n 219|// and the retired row is GONE from the broker's enumeration\n*220|// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221|#[test]\n 222|fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223|    init_home();\n...\n 252|    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\n*253|    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n 254|    let end = Instant::now() + Duration::from_secs(20);\n 255|    while peer_stream_ids(&mut probe).contains(&s1_a) {\n 256|        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n...\n 289|    // The fresh generation never touched the historical stream id — the\n*290|    // DISPATCH:<id> replay-storm line (the field evidence class) must be\n 291|    // absent for s1, while s2's re-serve is expected work, not noise.\n 292|    let gen2_log = gen2.stderr_log();\n 293|    assert!(\n*294|        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\n 295|        \"generation 2 replayed the retired historical stream {s1_a}: {gen2_log}\"\n 296|    );\n 297|    let _ = s2_a;\n...\n 302|\n*303|// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\n 304|// finished-UNRETIRED orderings the Served-report path can never see: the\n 305|// pre-existing backlog and the brain-down-detach window. s1 is attached and\n 306|// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\n*307|// operator detaches (the row finishes with no dispatcher alive; the conn\n 308|// stays open, so the conn-close sweep does not fire either). gen2 starts and\n 309|// serves a fresh current attach: the finished s1 row must be retired\n 310|// UNSERVED (the family-aware terminal check — pre-fix, the pinned opener\n...\n 313|// viewport keeps flowing across several polls, sees no EOF, and gen2's log\n*314|// never carries a DISPATCH:<s1> line; the s1 row leaves the table without\n 315|// ever being served (retire-on-sight drains the backlog).\n*316|// [int->REQ-REDISPATCH-FINISHED-RETIRE]\n 317|#[test]\n 318|fn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\n 319|    init_home();\n...\n 376|\n*377|    // Several seconds of live driving across many 50ms dispatcher polls — the\n 378|    // window in which a stale replay would land its steal.\n 379|    for round in 0..3u32 {\n 380|        let marker = format!(\"CUR-{round}\\r\");\n## brain_decouple.rs\n 6|//! A controller's writer thread does a BLOCKING socket write to its brain\n*7|//! subscriber connection. When that brain is SUSPENDED (doyle's rig:\n 8|//! `NtSuspendProcess` on the brain child) the write never returns. Pre-W2 the\n 9|//! output-driven eviction (`append` → `mark_controller_gone`, bounded by the 5 s\n 10|//! `CONTROLLER_WRITE_DEADLINE`) fires only on NEW output, and `reap_dead_controller`\n...\n 61|use spt_daemon::msg::{\n*62|    SpawnReq, Spawned, StallEvictsReply, SubscribeOutcome, SubscribeReq, SubscribedReply, KIND_SPAWN,\n 63|    KIND_SPAWNED, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY, KIND_SUBSCRIBE, KIND_SUBSCRIBED,\n 64|};\n 65|use spt_daemon::transport::{send_hello, LocalSocketTransport};\n...\n 165|            KIND_SUBSCRIBE,\n*166|            serde_json::to_value(SubscribeReq {\n 167|                session_id: sid,\n 168|                from_seq: 0,\n 169|                intent,\n...\n 236|\n*237|    // ── Spawner A: spawn the flood child (dispatch_spawn pre-attaches A as the\n 238|    //    LOCAL controller, by=None). ──\n 239|    let mut a = connect(&name);\n 240|    send(\n...\n 266|            KIND_SUBSCRIBE,\n*267|            serde_json::to_value(SubscribeReq {\n 268|                session_id: sid,\n 269|                from_seq: 0,\n 270|                intent: AttachIntent::Control,\n...\n 302|            KIND_SUBSCRIBE,\n*303|            serde_json::to_value(SubscribeReq {\n 304|                session_id: sid,\n 305|                from_seq: 0,\n 306|                intent: AttachIntent::Viewer,\n...\n 401|//   - a write on the old client connection FAILS (it was physically retired). ← RED pre-fix\n*402|// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the\n*403|// absolute deadline fires out of band, poisons the whole physical conn, aborts\n 404|// read+write so `handle_conn` reaches EOF cleanup, and a fresh controller resumes\n 405|// from the frozen cursor with NO lost frame.\n 406|//\n...\n 441|\n*442|/// One `KIND_SESSIONS` round-trip, returning the full reply. The dispatch runs\n 443|/// `reap_dead_controller` → the time-based `stall_evict_controller`, so polling\n 444|/// this both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 445|/// Bounded thread + channel (a wedge surfaces as a timeout, not a hang), like the\n...\n 518|            KIND_SUBSCRIBE,\n*519|            serde_json::to_value(SubscribeReq {\n 520|                session_id: sid,\n 521|                from_seq: 0,\n 522|                intent: AttachIntent::Control,\n...\n 637|    //    cursor for the sid row. ──\n*638|    // POISON-BEATS-EVICT (F-039 rider, doyle-ruled 2026-07-10, 3rd CI trip): the\n 639|    // ONE wedged controller's release can be tallied by the LOGICAL reap\n*640|    // (stall_evict_controller), by the PHYSICAL bound (the conn watchdog poisons\n 641|    // first and note_controller_write_retired records the same event class —\n 642|    // the field-accepted v0.30.6 behavior), or by BOTH when the watchdog races\n 643|    // the reap's blocked-window sample. So the tally can step 0→2 and an\n...\n 709|            KIND_SUBSCRIBE,\n*710|            serde_json::to_value(SubscribeReq {\n 711|                session_id: sid,\n 712|                from_seq: resume_seq,\n 713|                intent: AttachIntent::Control,\n...\n 790|    // ONE wedged controller, one release — observed as one tally, or two when the\n*791|    // physical poison races the logical reap (poison-beats-evict, see step 6).\n 792|    // Three or more would be a genuine second eviction: still a failure.\n 793|    assert!(\n 794|        (1..=2).contains(&stall_evicts_seen),\n 795|        \"the one wedged controller's release must be tallied once (logical reap OR \\\n*796|         physical poison) or twice (both raced — poison-beats-evict, field-accepted \\\n 797|         v0.30.6); got {stall_evicts_seen}\"\n 798|    );\n 799|    // ── THE RED (pre-fix): logical eviction freed the ROLE, but the black-holed\n...\n 807|         → 0) within 2 s of logical release, WITHOUT the client dropping its end — the \\\n*808|         bounded/cancelable write must poison+close the physical conn. PRE-FIX RED: the \\\n 809|         detached writer stays wedged in write_frame and the handler never exits.\"\n 810|    );\n 811|    // ── THE RED (pre-fix): a write on the old client conn must FAIL — the physical"}],"details":{"scopePath":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs, crates/spt-daemon/tests/redispatch.rs, crates/spt-daemon/tests/brain_decouple.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\tests","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":49,"fileCount":3,"files":["crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","crates/spt-daemon/tests/redispatch.rs","crates/spt-daemon/tests/brain_decouple.rs"],"fileMatches":[{"path":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","count":13},{"path":"crates/spt-daemon/tests/redispatch.rs","count":20},{"path":"crates/spt-daemon/tests/brain_decouple.rs","count":16}],"truncated":true,"perFileLimitReached":20,"displayContent":"# crates/spt-daemon/tests/\n## conn_blackhole_lifecycle.rs\n  13│//! write timeout) and, against CURRENT v0.30.6 semantics (`crates/spt-daemon/\n *14│//! src/conn.rs` bounded/cancelable/poison-on-failure writes, landed fc048bb),\n  15│//! asserts hertz's FIVE invariants for ONE incident:\n  16│//!\n  17│//!   1. UNRELATED SESSIONS CONTINUE — a second, independent session's viewer keeps\n    │...\n  31│//!      session COMPLETES and RECEIVES replayed output from the frozen cursor\n *32│//!      (the fan-out accepts + serves a new subscriber after retirement);\n  33│//!   5. NO LOCK OR TASK REMAINS OWNED BY THE RETIRED CONNECTION — asserted via\n  34│//!      PROXIES (this invariant has no direct seam): after retirement the broker\n  35│//!      still answers a control-plane `KIND_SESSIONS` request PROMPTLY (no lock\n    │...\n  62│//! `set_timeout` → `Unsupported`) — the exact field failure. On Unix the\n *63│//! poison/retire path is a `shutdown(SHUT_RDWR)` abort of a buffer-fill block: a\n  64│//! genuinely DIFFERENT mechanism, not this incident. Faking a Unix companion\n  65│//! would test a different thing, so — mirroring the r4 gate's own\n  66│//! `#[cfg(windows)]` leg — the entire harness is Windows-only. On other targets\n    │...\n  90│    decode_bytes, encode_bytes, InputReq, OutputEvent, SessionsReply, SpawnReq, Spawned,\n *91│    StallEvictsReply, SubscribeOutcome, SubscribeReq, SubscribedReply, KIND_INPUT, KIND_OUTPUT,\n  92│    KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN, KIND_SPAWNED, KIND_STALL_EVICTS,\n  93│    KIND_STALL_EVICTS_REPLY, KIND_SUBSCRIBE, KIND_SUBSCRIBED,\n  94│};\n    │...\n 221│\n*222│/// One `KIND_SESSIONS` round-trip, returning the reply. The dispatch runs the\n 223│/// `reap_dead_controller` → time-based `stall_evict_controller`, so polling this\n 224│/// both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 225│/// Bounded thread + channel. Verbatim sibling of the r4 gate helper.\n    │...\n 276│            KIND_SUBSCRIBE,\n*277│            serde_json::to_value(SubscribeReq {\n 278│                session_id: sid_u,\n 279│                from_seq: 0,\n 280│                intent: AttachIntent::Viewer,\n    │...\n 312│            KIND_SUBSCRIBE,\n*313│            serde_json::to_value(SubscribeReq {\n 314│                session_id: sid_b,\n 315│                from_seq: 0,\n 316│                intent: AttachIntent::Control,\n    │...\n 437│    //    stall-evict). Record t_release + freeze the resume cursor for the sid_b\n*438│    //    row. Accept a tally of {1, 2}: POISON-BEATS-EVICT (F-039 rider,\n*439│    //    doyle-ruled) — the physical watchdog can poison first and record the same\n 440│    //    release class, so the tally can step 0→2 when watchdog races the reap. ──\n 441│    let mut stall_evicts_seen: u64 = 0;\n 442│    let mut t_release: Option<Instant> = None;\n    │...\n 502│    //    bound): a write on the OLD client Stream must FAIL — the physical conn\n*503│    //    was retired (poisoned + severed), not merely logically stall-evicted. ──\n 504│    let old_client_write: Option<std::io::Result<()>> = r_stream.as_mut().map(|s| {\n 505│        write_frame(s, &Envelope::new(KIND_SESSIONS, serde_json::Value::Null))\n 506│    });\n    │...\n 535│            KIND_SUBSCRIBE,\n*536│            serde_json::to_value(SubscribeReq {\n 537│                session_id: sid_b,\n 538│                from_seq: resume_seq,\n 539│                intent: AttachIntent::Viewer,\n    │...\n 623│        \"HARNESS: the one wedged controller's release must be tallied once (logical \\\n*624│         reap OR physical poison) or twice (both raced — poison-beats-evict, \\\n 625│         field-accepted v0.30.6); got {stall_evicts_seen}\"\n 626│    );\n 627│\n    │...\n 685│        \"INVARIANT 4: the fresh viewer must RECEIVE replayed output from the frozen \\\n*686│         cursor (resume_seq={resume_seq}) — the fan-out must serve a new subscriber \\\n 687│         after retirement; got fv_got_output={fv_got_output} fv_first_seq={fv_first_seq:?}\"\n 688│    );\n 689│\n## redispatch.rs\n  *1│//! REDISPATCH-TRUTH W1 — the three production-path regressions (ADR-0038 D,\n   2│//! KNOWN-HAZARDS 7.41): every leg drives the REAL\n  *3│//! `dispatch::run_dispatch_loop` (as a killable `dispatch_fixture` child —\n  *4│//! a dispatcher GENERATION whose death drops its workers' broker conns\n   5│//! exactly like a production brain death). NO manual re-serve anywhere: the\n   6│//! pre-fix e2e (`attach_survives_target_brain_restart_exactly_once`)\n   7│//! hand-stepped a known stream and bypassed exactly the rediscovery/\n    │...\n  10│//! - D1: a FINISHED historical Attach + a CURRENT active Attach, same\n *11│//!   endpoint/origin; restart the dispatcher generation only → the\n  12│//!   historical stream neither takes nor clears the current controller\n  13│//!   (post-restart input still renders; no EOF; no replay-storm log line;\n  14│//!   the retired row left the broker's enumeration).\n  15│//! - D2: an ACTIVE Attach whose opener rolled out of the bounded ring;\n *16│//!   restart the dispatcher → durable classification (the pinned opener)\n  17│//!   resumes the SAME operator stream.\n  18│//! - D3: ONE injected transient worker-start failure → the claim requeues\n  19│//!   and the retry serves the stream (no permanent abandonment, no\n    │...\n 108│\n*109│/// One REAL dispatcher generation: the `dispatch_fixture` child running\n*110│/// production `run_dispatch_loop` against `broker_name`. Killed = brain death\n 111│/// (worker conns drop). Stderr lands in `log` for replay-storm assertions.\n 112│struct Generation {\n 113│    child: Child,\n    │...\n 119│        let f = std::fs::File::create(&log).expect(\"gen stderr log\");\n*120│        let mut cmd = Command::new(env!(\"CARGO_BIN_EXE_dispatch_fixture\"));\n 121│        cmd.arg(broker_name)\n 122│            .env(\"SPT_HOME\", HOME.get().expect(\"home\").path())\n 123│            .stdout(Stdio::null())\n    │...\n 127│        }\n*128│        let child = cmd.spawn().expect(\"spawn dispatch_fixture\");\n 129│        Generation { child, log }\n 130│    }\n 131│\n    │...\n 211│\n*212│// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1 — the field bug, end to end\n 213│// through PRODUCTION rediscovery: a finished historical Attach and a current\n*214│// active Attach on the same endpoint/origin; only the dispatcher generation\n 215│// restarts. The historical stream must neither take nor clear the current\n 216│// controller: post-restart input still renders (the frozen-PTY symptom is\n 217│// the failure), the operator sees NO EOF (the replayed-EOF detach is the\n    │...\n 219│// and the retired row is GONE from the broker's enumeration\n*220│// (REQ-REDISPATCH-FINISHED-RETIRE observed at the seam that matters).\n 221│#[test]\n 222│fn finished_historical_attach_neither_takes_nor_clears_current_controller() {\n 223│    init_home();\n    │...\n 252│    // Served-to-completion → the worker retires the row (ADR-0038 A). This is\n*253│    // the enumeration a FRESH dispatcher would replay from — it must shrink.\n 254│    let end = Instant::now() + Duration::from_secs(20);\n 255│    while peer_stream_ids(&mut probe).contains(&s1_a) {\n 256│        assert!(Instant::now() < end, \"the finished attach row never retired from the table\");\n    │...\n 289│    // The fresh generation never touched the historical stream id — the\n*290│    // DISPATCH:<id> replay-storm line (the field evidence class) must be\n 291│    // absent for s1, while s2's re-serve is expected work, not noise.\n 292│    let gen2_log = gen2.stderr_log();\n 293│    assert!(\n*294│        !gen2_log.contains(&format!(\"DISPATCH:{s1_a}:\")),\n 295│        \"generation 2 replayed the retired historical stream {s1_a}: {gen2_log}\"\n 296│    );\n 297│    let _ = s2_a;\n    │...\n 302│\n*303│// [int->REQ-HAZARD-REDISPATCH-CONTROL-STEAL] D1b (gate round 1) — the\n 304│// finished-UNRETIRED orderings the Served-report path can never see: the\n 305│// pre-existing backlog and the brain-down-detach window. s1 is attached and\n 306│// driven under gen1, gen1 is KILLED mid-serve (no Served report), THEN the\n*307│// operator detaches (the row finishes with no dispatcher alive; the conn\n 308│// stays open, so the conn-close sweep does not fire either). gen2 starts and\n 309│// serves a fresh current attach: the finished s1 row must be retired\n 310│// UNSERVED (the family-aware terminal check — pre-fix, the pinned opener\n    │...\n 313│// viewport keeps flowing across several polls, sees no EOF, and gen2's log\n*314│// never carries a DISPATCH:<s1> line; the s1 row leaves the table without\n 315│// ever being served (retire-on-sight drains the backlog).\n*316│// [int->REQ-REDISPATCH-FINISHED-RETIRE]\n 317│#[test]\n 318│fn finished_unretired_attach_is_skipped_and_drained_not_replayed() {\n 319│    init_home();\n    │...\n 376│\n*377│    // Several seconds of live driving across many 50ms dispatcher polls — the\n 378│    // window in which a stale replay would land its steal.\n 379│    for round in 0..3u32 {\n 380│        let marker = format!(\"CUR-{round}\\r\");\n## brain_decouple.rs\n   6│//! A controller's writer thread does a BLOCKING socket write to its brain\n  *7│//! subscriber connection. When that brain is SUSPENDED (doyle's rig:\n   8│//! `NtSuspendProcess` on the brain child) the write never returns. Pre-W2 the\n   9│//! output-driven eviction (`append` → `mark_controller_gone`, bounded by the 5 s\n  10│//! `CONTROLLER_WRITE_DEADLINE`) fires only on NEW output, and `reap_dead_controller`\n    │...\n  61│use spt_daemon::msg::{\n *62│    SpawnReq, Spawned, StallEvictsReply, SubscribeOutcome, SubscribeReq, SubscribedReply, KIND_SPAWN,\n  63│    KIND_SPAWNED, KIND_STALL_EVICTS, KIND_STALL_EVICTS_REPLY, KIND_SUBSCRIBE, KIND_SUBSCRIBED,\n  64│};\n  65│use spt_daemon::transport::{send_hello, LocalSocketTransport};\n    │...\n 165│            KIND_SUBSCRIBE,\n*166│            serde_json::to_value(SubscribeReq {\n 167│                session_id: sid,\n 168│                from_seq: 0,\n 169│                intent,\n    │...\n 236│\n*237│    // ── Spawner A: spawn the flood child (dispatch_spawn pre-attaches A as the\n 238│    //    LOCAL controller, by=None). ──\n 239│    let mut a = connect(&name);\n 240│    send(\n    │...\n 266│            KIND_SUBSCRIBE,\n*267│            serde_json::to_value(SubscribeReq {\n 268│                session_id: sid,\n 269│                from_seq: 0,\n 270│                intent: AttachIntent::Control,\n    │...\n 302│            KIND_SUBSCRIBE,\n*303│            serde_json::to_value(SubscribeReq {\n 304│                session_id: sid,\n 305│                from_seq: 0,\n 306│                intent: AttachIntent::Viewer,\n    │...\n 401│//   - a write on the old client connection FAILS (it was physically retired). ← RED pre-fix\n*402│// Post-fix (conn.rs bounded/cancelable/poison-on-failure framed write): the\n*403│// absolute deadline fires out of band, poisons the whole physical conn, aborts\n 404│// read+write so `handle_conn` reaches EOF cleanup, and a fresh controller resumes\n 405│// from the frozen cursor with NO lost frame.\n 406│//\n    │...\n 441│\n*442│/// One `KIND_SESSIONS` round-trip, returning the full reply. The dispatch runs\n 443│/// `reap_dead_controller` → the time-based `stall_evict_controller`, so polling\n 444│/// this both DRIVES the logical eviction and reads the frozen `resume_seq` cursor.\n 445│/// Bounded thread + channel (a wedge surfaces as a timeout, not a hang), like the\n    │...\n 518│            KIND_SUBSCRIBE,\n*519│            serde_json::to_value(SubscribeReq {\n 520│                session_id: sid,\n 521│                from_seq: 0,\n 522│                intent: AttachIntent::Control,\n    │...\n 637│    //    cursor for the sid row. ──\n*638│    // POISON-BEATS-EVICT (F-039 rider, doyle-ruled 2026-07-10, 3rd CI trip): the\n 639│    // ONE wedged controller's release can be tallied by the LOGICAL reap\n*640│    // (stall_evict_controller), by the PHYSICAL bound (the conn watchdog poisons\n 641│    // first and note_controller_write_retired records the same event class —\n 642│    // the field-accepted v0.30.6 behavior), or by BOTH when the watchdog races\n 643│    // the reap's blocked-window sample. So the tally can step 0→2 and an\n    │...\n 709│            KIND_SUBSCRIBE,\n*710│            serde_json::to_value(SubscribeReq {\n 711│                session_id: sid,\n 712│                from_seq: resume_seq,\n 713│                intent: AttachIntent::Control,\n    │...\n 790│    // ONE wedged controller, one release — observed as one tally, or two when the\n*791│    // physical poison races the logical reap (poison-beats-evict, see step 6).\n 792│    // Three or more would be a genuine second eviction: still a failure.\n 793│    assert!(\n 794│        (1..=2).contains(&stall_evicts_seen),\n 795│        \"the one wedged controller's release must be tallied once (logical reap OR \\\n*796│         physical poison) or twice (both raced — poison-beats-evict, field-accepted \\\n 797│         v0.30.6); got {stall_evicts_seen}\"\n 798│    );\n 799│    // ── THE RED (pre-fix): logical eviction freed the ROLE, but the black-holed\n    │...\n 807│         → 0) within 2 s of logical release, WITHOUT the client dropping its end — the \\\n*808│         bounded/cancelable write must poison+close the physical conn. PRE-FIX RED: the \\\n 809│         detached writer stays wedged in write_frame and the handler never exits.\"\n 810│    );\n 811│    // ── THE RED (pre-fix): a write on the old client conn must FAIL — the physical"},"isError":false,"timestamp":1784247567905}}
{"type":"message","id":"b5dd009a","parentId":"44e4f224","timestamp":"2026-07-17T00:19:32.030Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Requesting full read_event transport function**"},{"type":"toolCall","id":"call_6tCEKXNp04NJQRY7dl4Y8BII|fc_0ceecce325b7fe2d016a597512c5ac819888d2214758fb9d8e","name":"grep","arguments":{"i":"Locating blocking read implementation","pattern":"fn recv_envelope_until|read_event_until|BrainConn::Whole|recv_timeout","path":"crates/spt-daemon/src/brain.rs","selector":"650-850","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":11356,"output":88,"cacheRead":59904,"cacheWrite":0,"totalTokens":71348,"reasoningTokens":12,"cost":{"input":0.056780000000000004,"output":0.00264,"cacheRead":0.029952,"cacheWrite":0,"total":0.08937200000000001}},"stopReason":"toolUse","timestamp":1784247567913,"responseId":"resp_0ceecce325b7fe2d016a59751012a08198bc739f721e931db2","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a597511f234819885450b0fd74016b7","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXUSzlleqgmnEjGN8pv1aO8o6JlD1jPNNU6iUHxNTkwt5FEPE9Gvs46v0uRF15H6JpacoyeVnzsFmNKwDVq0JIzgn_6NjIOF0dKwXCAei3DstTb4wcbXfGyjKidhVUAHCijIowA0sUO5dEMxz7C_GZibKobVX-FK6KYzyRZ82HnC2k0AjNQHXBOA51Wvcp-FkVXUHIu0qvVoDRhNu8DKlWiwLWnXLHRbACqy8f3n1LMVvVJM2-5qi6t3LHc0Tl2zHU0Jlk5w_nugxNWHU-hl7jEX8ph0mGzZ0WpF1Aeqx6FIJ6ZLaWTgC2NYrBjdTGFSMNFLF5_KMwMvVl5sZqQZBEmbOg2eYq4wKZGZh1qsduV45EnG-snMWrvR2qPah75l4gE7xBJyMckTOcy322OmbJmfcvRicyBVtVrEz6qp2ew2p4GFI-35AVJ7tTTlQoJ8kn8Y7D3Pysif3ZxmMO-i420EDA2xQyn6kz_ZoUwK9sGCFhiXbJ36UCGVFPdi_m9wQIKxp5y8ajr1qrZBRDqNwRfl-tXXezej-8W_pH8RKrwAjCxQHRjxm2FSpqde7Mr6eGsSCmBS0Q_byMiTTg64R8ojIljzoCYZRnyJZ8BkF73eo9cPxJ07fHyufHt1dJTfx4yetW0dGFhK5tSmtoUiaktuH5F_594n5WZQiHm2OObnovRMaX3JuBDG8ZG6ECdrxFfdSTZ8Vf-z8pvIvz7KiUEwFBdtnjKNw45fSTzl81N5nJtpvPN9Aj6tu9qLQ_yfUPBd9FbQSYZgH3rIXYhhApZziEX3O1MTsmegqNpAF4zaHfcuz5C6dKpl3E9pefeYRIyEVwiCLf7ey3x53Ky-UGhnpNmbg9uoH9zxmMDekeuNZyOen6XRwZ8pUenZ0K7Yq_AGgheOwBf7r7WKbGgwg9Bap3GyPW2tgHkxmk200xjsTTbXauHIDMNNQWhc4JMxyyoqqMwVyPD1clgQRZNLYO8l7erTkIBS1EEWSLRrloFHJMI=","summary":[{"type":"summary_text","text":"**Requesting full read_event transport function**"}]},{"id":"fc_0ceecce325b7fe2d016a597512c5ac819888d2214758fb9d8e","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating blocking read implementation\",\"pattern\":\"fn recv_envelope_until|read_event_until|BrainConn::Whole|recv_timeout\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"650-850\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_6tCEKXNp04NJQRY7dl4Y8BII","name":"grep"}]},"duration":4117.05680000037,"ttft":2025.0619999989867,"contextSnapshot":{"promptTokens":71260,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_6tCEKXNp04NJQRY7dl4Y8BII|fc_0ceecce325b7fe2d016a597512c5ac819888d2214758fb9d8e","toolName":"grep","startedAt":"2026-07-17T00:19:32.030Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Locating blocking read implementation"},"id":"d9b4edd3","parentId":"b5dd009a","timestamp":"2026-07-17T00:19:32.030Z"}
{"type":"message","id":"cc6f81a1","parentId":"d9b4edd3","timestamp":"2026-07-17T00:19:32.032Z","message":{"role":"toolResult","toolCallId":"call_6tCEKXNp04NJQRY7dl4Y8BII|fc_0ceecce325b7fe2d016a597512c5ac819888d2214758fb9d8e","toolName":"grep","content":[{"type":"text","text":"No matches found"}],"details":{"scopePath":"crates/spt-daemon/src/brain.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/brain.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":0,"fileCount":0,"files":[],"truncated":false},"isError":false,"useless":true,"timestamp":1784247572032}}
{"type":"message","id":"a595670e","parentId":"cc6f81a1","timestamp":"2026-07-17T00:19:35.213Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":""},{"type":"toolCall","id":"call_W5mcbkRlwVMXj1y3aXyHgtfo|fc_0ceecce325b7fe2d016a5975160ec881988c83d3138781e7d1","name":"grep","arguments":{"i":"Finding event reader methods","pattern":"pub fn read_event|read_frame\\(","path":"crates/spt-daemon/src/brain.rs","selector":"500-850","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3266,"output":73,"cacheRead":68096,"cacheWrite":0,"totalTokens":71435,"reasoningTokens":7,"cost":{"input":0.01633,"output":0.00219,"cacheRead":0.034048,"cacheWrite":0,"total":0.052568000000000004}},"stopReason":"toolUse","timestamp":1784247572047,"responseId":"resp_0ceecce325b7fe2d016a5975145a788198a6246734ec2b3eb2","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a59751567608198a90906eaaade65b5","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXUWSuD0SyRxKTe0AdCT3uRYwyLCRWc2_ll1lxu7pV8G8X6HMyY2vCrjFWd3p5TQ0gNCpvr8FPQan0IE_PFic6BUXeHm7NrKjqm8xf8shX0Nw-a7a-TXFVg7r7XExX-7na2hYdd39XAX6ZNbkE2TbbNCeynUs4u6jwBDUM8q65pEKvYY3MG12dxJwwiipovUCDvMA1N5tMp8EUu4N8oUoO8IO_x5a742m4Io2286Flcs2_rscdjkfBeWCP61h0z-5F5cmR_EfniMXdnSSMJsxE5_8sVvo0Yha4nnF_HEwNqwvTtjA6UJ-rGH4EtpfI5Y0QprgBtcKG2mQ9rT4zN1HGJIawA-fQllWCpKbD_5rmQLg3gcdG04lkdaB8OS4ePs8S9zzWFdAtd_S5k8aGB2wZc4IklohSdc041uxwPFdcregYUAANPff-udu4lno5Ltz3Ost5dknyOGfOXL2SfZ8qtcguTYRpPX2mVQWMbdEgzcgeq1NFXT3E8wMO9QqBFoTmQfsKwpmH_flHaUWOOEDYnNlU7o-U2BfYZHyLcjg68wbfwsla6OjR_WKQyeSA1RVDAYkwMs0RcffmRMhwiCWWMHj16s8kr4pukLnGmkiy7FXHq_YAGkv4utHQ8ufg9KJsGHeglVCzF5ccLYBtfi3YhjvH2Rz3YygOvx08aGGhrBqg1Ib46Rjp8PObhgi9Xwiz-Lsdr84OwMVfwlvE2ddkqbxCz_t5_9teYfw9vR0kz_-EHnYwDWJBcQzM_TY8FHkiKt2_ct2s7V3RCRMmpVDZ_rckp3tFt7uBp8XEj8ib9lXfyIJW3EC6BtWhvJSklN2-Xiy4XOPUv5TQGWMSxs95F3BcSe6DHxae9jPYM-gpRSWtaWcvrvMY9wQPsJ0Np1PTfbsMmkirSxFtQ9Y4lPMg30q9PwuHfnn4Js-pUH63oddjL98vkYs5uK3i9pVt3smvUI","summary":[]},{"id":"fc_0ceecce325b7fe2d016a5975160ec881988c83d3138781e7d1","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding event reader methods\",\"pattern\":\"pub fn read_event|read_frame\\\\(\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"500-850\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_W5mcbkRlwVMXj1y3aXyHgtfo","name":"grep"}]},"duration":3157.8950999975204,"ttft":1799.5500999987125,"contextSnapshot":{"promptTokens":71362,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_W5mcbkRlwVMXj1y3aXyHgtfo|fc_0ceecce325b7fe2d016a5975160ec881988c83d3138781e7d1","toolName":"grep","startedAt":"2026-07-17T00:19:35.214Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Finding event reader methods"},"id":"fd26cc0a","parentId":"a595670e","timestamp":"2026-07-17T00:19:35.214Z"}
{"type":"message","id":"eea8a36b","parentId":"fd26cc0a","timestamp":"2026-07-17T00:19:35.219Z","message":{"role":"toolResult","toolCallId":"call_W5mcbkRlwVMXj1y3aXyHgtfo|fc_0ceecce325b7fe2d016a5975160ec881988c83d3138781e7d1","toolName":"grep","content":[{"type":"text","text":" 624|\n*625|    pub fn read_event(&mut self) -> io::Result<BrokerEvent> {\n 626|        self.read_event_until(None)\n 627|    }\n 628|\n...\n 635|    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n*636|    pub fn read_event_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n 637|        loop {\n 638|            let env = self.read_frame_until(deadline)?;\n 639|            match env.kind.as_str() {"}],"details":{"scopePath":"crates/spt-daemon/src/brain.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/brain.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":2,"fileCount":1,"files":["crates/spt-daemon/src/brain.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/brain.rs","count":2}],"truncated":false,"displayContent":" 624│\n*625│    pub fn read_event(&mut self) -> io::Result<BrokerEvent> {\n 626│        self.read_event_until(None)\n 627│    }\n 628│\n    │...\n 635│    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n*636│    pub fn read_event_until(&mut self, deadline: Option<Instant>) -> io::Result<BrokerEvent> {\n 637│        loop {\n 638│            let env = self.read_frame_until(deadline)?;\n 639│            match env.kind.as_str() {"},"isError":false,"timestamp":1784247575218}}
{"type":"message","id":"4d8f304a","parentId":"eea8a36b","timestamp":"2026-07-17T00:19:38.847Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_tn6wGWXtfTLaqeD4TM82efmc|fc_0ceecce325b7fe2d016a597519731c8198acbfd5bd9e41aac6","name":"grep","arguments":{"i":"Finding frame deadline helper","pattern":"fn read_frame_until","path":"crates/spt-daemon/src/brain.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3473,"output":58,"cacheRead":68096,"cacheWrite":0,"totalTokens":71627,"cost":{"input":0.017365000000000002,"output":0.00174,"cacheRead":0.034048,"cacheWrite":0,"total":0.053153000000000006}},"stopReason":"toolUse","timestamp":1784247575222,"responseId":"resp_0ceecce325b7fe2d016a59751763d08198997d14ddf13cebba","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_0ceecce325b7fe2d016a597519731c8198acbfd5bd9e41aac6","type":"function_call","status":"completed","arguments":"{\"i\":\"Finding frame deadline helper\",\"pattern\":\"fn read_frame_until\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_tn6wGWXtfTLaqeD4TM82efmc","name":"grep"}]},"duration":3594.907800003886,"ttft":2218.343200005591,"contextSnapshot":{"promptTokens":71569,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_tn6wGWXtfTLaqeD4TM82efmc|fc_0ceecce325b7fe2d016a597519731c8198acbfd5bd9e41aac6","toolName":"grep","startedAt":"2026-07-17T00:19:38.847Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Finding frame deadline helper"},"id":"89908aaa","parentId":"4d8f304a","timestamp":"2026-07-17T00:19:38.847Z"}
{"type":"message","id":"6abc62f4","parentId":"89908aaa","timestamp":"2026-07-17T00:19:38.851Z","message":{"role":"toolResult","toolCallId":"call_tn6wGWXtfTLaqeD4TM82efmc|fc_0ceecce325b7fe2d016a597519731c8198acbfd5bd9e41aac6","toolName":"grep","content":[{"type":"text","text":" 1744|    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n*1745|    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {\n 1746|        match &mut self.conn {\n 1747|            BrainConn::Whole(stream) => read_frame(stream),\n 1748|            BrainConn::Split { frames, .. } => match deadline {"}],"details":{"scopePath":"crates/spt-daemon/src/brain.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":1,"fileCount":1,"files":["crates/spt-daemon/src/brain.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/brain.rs","count":1}],"truncated":false,"displayContent":" 1744│    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n*1745│    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {\n 1746│        match &mut self.conn {\n 1747│            BrainConn::Whole(stream) => read_frame(stream),\n 1748│            BrainConn::Split { frames, .. } => match deadline {"},"isError":false,"timestamp":1784247578851}}
{"type":"message","id":"ed7ec33d","parentId":"6abc62f4","timestamp":"2026-07-17T00:19:41.333Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_kLKZNwj5Dmli75iqF6ktr8IM|fc_0ceecce325b7fe2d016a59751c3db0819899387c6578474d3e","name":"read","arguments":{"i":"Reading whole connection timeout behavior","path":"crates/spt-daemon/src/brain.rs","selector":"1735-1775"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3636,"output":42,"cacheRead":68096,"cacheWrite":0,"totalTokens":71774,"cost":{"input":0.01818,"output":0.00126,"cacheRead":0.034048,"cacheWrite":0,"total":0.05348800000000001}},"stopReason":"toolUse","timestamp":1784247578865,"responseId":"resp_0ceecce325b7fe2d016a59751b31c0819887a0a76970b934cb","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_0ceecce325b7fe2d016a59751c3db0819899387c6578474d3e","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading whole connection timeout behavior\",\"path\":\"crates/spt-daemon/src/brain.rs\",\"selector\":\"1735-1775\"}","call_id":"call_kLKZNwj5Dmli75iqF6ktr8IM","name":"read"}]},"duration":2457.3483000025153,"ttft":1371.4054000005126,"contextSnapshot":{"promptTokens":71732,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_kLKZNwj5Dmli75iqF6ktr8IM|fc_0ceecce325b7fe2d016a59751c3db0819899387c6578474d3e","toolName":"read","startedAt":"2026-07-17T00:19:41.334Z","args":{"path":"crates/spt-daemon/src/brain.rs"},"intent":"Reading whole connection timeout behavior"},"id":"d46e4d08","parentId":"ed7ec33d","timestamp":"2026-07-17T00:19:41.334Z"}
{"type":"message","id":"1de79615","parentId":"d46e4d08","timestamp":"2026-07-17T00:19:41.351Z","message":{"role":"toolResult","toolCallId":"call_kLKZNwj5Dmli75iqF6ktr8IM|fc_0ceecce325b7fe2d016a59751c3db0819899387c6578474d3e","toolName":"read","content":[{"type":"text","text":"    ///\n    /// - [`BrainConn::Whole`] — the ordinary blocking client: a direct\n    ///   [`read_frame`]; `deadline` is irrelevant (a whole brain never sets\n    ///   `io_timeout`, so callers always pass `None` here).\n    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns\n    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]\n    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]\n    ///   — the poison signal the pump escalates to a supervised restart (NEVER a\n    ///   per-peer retry). A `Disconnected` channel means the reader thread exited\n    ///   (the conn hung up); surface it as `UnexpectedEof`.\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\n    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {\n        match &mut self.conn {\n            BrainConn::Whole(stream) => read_frame(stream),\n            BrainConn::Split { frames, .. } => match deadline {\n                Some(dl) => {\n                    // Total-wait bound: each recv is the REMAINING budget to the\n                    // call's entry deadline, so unrelated frames dripping in\n                    // cannot reset it (the per-read-timeout wedge the ruling\n                    // forbids). A non-positive remaining → recv_timeout(ZERO),\n                    // which still drains an already-queued frame before timing out.\n                    let remaining = dl.saturating_duration_since(Instant::now());\n                    match frames.recv_timeout(remaining) {\n                        Ok(framed) => framed,\n                        Err(RecvTimeoutError::Timeout) => Err(io::Error::new(\n                            io::ErrorKind::TimedOut,\n                            \"brain IPC read deadline elapsed\",\n                        )),\n                        Err(RecvTimeoutError::Disconnected) => Err(io::Error::new(\n                            io::ErrorKind::UnexpectedEof,\n                            \"brain IPC reader thread ended (conn closed)\",\n                        )),\n                    }\n                }\n                None => frames.recv().map_err(|_| {\n                    io::Error::new(\n                        io::ErrorKind::UnexpectedEof,\n                        \"brain IPC reader thread ended (conn closed)\",\n                    )\n                })?,\n            },\n        }\n    }\n\n    fn require_session(&self) -> io::Result<u64> {\n…\n    }\n\n[Showing lines 1734-1778 of 2236. Use :1779 to continue]"}],"details":{"truncation":{"content":"    ///\r\n    /// - [`BrainConn::Whole`] — the ordinary blocking client: a direct\r\n    ///   [`read_frame`]; `deadline` is irrelevant (a whole brain never sets\r\n    ///   `io_timeout`, so callers always pass `None` here).\r\n    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns\r\n    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]\r\n    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]\r\n    ///   — the poison signal the pump escalates to a supervised restart (NEVER a\r\n    ///   per-peer retry). A `Disconnected` channel means the reader thread exited\r\n    ///   (the conn hung up); surface it as `UnexpectedEof`.\r\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {\r\n        match &mut self.conn {\r\n            BrainConn::Whole(stream) => read_frame(stream),\r\n            BrainConn::Split { frames, .. } => match deadline {\r\n                Some(dl) => {\r\n                    // Total-wait bound: each recv is the REMAINING budget to the\r\n                    // call's entry deadline, so unrelated frames dripping in\r\n                    // cannot reset it (the per-read-timeout wedge the ruling\r\n                    // forbids). A non-positive remaining → recv_timeout(ZERO),\r\n                    // which still drains an already-queued frame before timing out.\r\n                    let remaining = dl.saturating_duration_since(Instant::now());\r\n                    match frames.recv_timeout(remaining) {\r\n                        Ok(framed) => framed,\r\n                        Err(RecvTimeoutError::Timeout) => Err(io::Error::new(\r\n                            io::ErrorKind::TimedOut,\r\n                            \"brain IPC read deadline elapsed\",\r\n                        )),\r\n                        Err(RecvTimeoutError::Disconnected) => Err(io::Error::new(\r\n                            io::ErrorKind::UnexpectedEof,\r\n                            \"brain IPC reader thread ended (conn closed)\",\r\n                        )),\r\n                    }\r\n                }\r\n                None => frames.recv().map_err(|_| {\r\n                    io::Error::new(\r\n                        io::ErrorKind::UnexpectedEof,\r\n                        \"brain IPC reader thread ended (conn closed)\",\r\n                    )\r\n                })?,\r\n            },\r\n        }\r\n    }\r\n\r\n    fn require_session(&self) -> io::Result<u64> {\r","truncated":true,"truncatedBy":"lines","totalLines":503,"totalBytes":2496,"outputLines":45,"outputBytes":2496,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    ///\r\n    /// - [`BrainConn::Whole`] — the ordinary blocking client: a direct\r\n    ///   [`read_frame`]; `deadline` is irrelevant (a whole brain never sets\r\n    ///   `io_timeout`, so callers always pass `None` here).\r\n    /// - [`BrainConn::Split`] — the pump carrier: the reader thread already owns\r\n    ///   the blocking read, so a bounded read is just [`Receiver::recv_timeout`]\r\n    ///   on the remaining budget. A `Timeout` surfaces [`io::ErrorKind::TimedOut`]\r\n    ///   — the poison signal the pump escalates to a supervised restart (NEVER a\r\n    ///   per-peer retry). A `Disconnected` channel means the reader thread exited\r\n    ///   (the conn hung up); surface it as `UnexpectedEof`.\r\n    // [impl->REQ-HAZARD-PUMP-IPC-DEADLINE]\r\n    fn read_frame_until(&mut self, deadline: Option<Instant>) -> io::Result<Envelope> {\r\n        match &mut self.conn {\r\n            BrainConn::Whole(stream) => read_frame(stream),\r\n            BrainConn::Split { frames, .. } => match deadline {\r\n                Some(dl) => {\r\n                    // Total-wait bound: each recv is the REMAINING budget to the\r\n                    // call's entry deadline, so unrelated frames dripping in\r\n                    // cannot reset it (the per-read-timeout wedge the ruling\r\n                    // forbids). A non-positive remaining → recv_timeout(ZERO),\r\n                    // which still drains an already-queued frame before timing out.\r\n                    let remaining = dl.saturating_duration_since(Instant::now());\r\n                    match frames.recv_timeout(remaining) {\r\n                        Ok(framed) => framed,\r\n                        Err(RecvTimeoutError::Timeout) => Err(io::Error::new(\r\n                            io::ErrorKind::TimedOut,\r\n                            \"brain IPC read deadline elapsed\",\r\n                        )),\r\n                        Err(RecvTimeoutError::Disconnected) => Err(io::Error::new(\r\n                            io::ErrorKind::UnexpectedEof,\r\n                            \"brain IPC reader thread ended (conn closed)\",\r\n                        )),\r\n                    }\r\n                }\r\n                None => frames.recv().map_err(|_| {\r\n                    io::Error::new(\r\n                        io::ErrorKind::UnexpectedEof,\r\n                        \"brain IPC reader thread ended (conn closed)\",\r\n                    )\r\n                })?,\r\n            },\r\n        }\r\n    }\r\n\r\n    fn require_session(&self) -> io::Result<u64> {\r\n…\n    }","startLine":1734,"lineNumbers":[1734,1735,1736,1737,1738,1739,1740,1741,1742,1743,1744,1745,1746,1747,1748,1749,1750,1751,1752,1753,1754,1755,1756,1757,1758,1759,1760,1761,1762,1763,1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776,1777,1778,null,1781]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\brain.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2236,"totalBytes":2496,"outputLines":45,"outputBytes":2496,"shownRange":{"start":1734,"end":1778},"nextOffset":1779}}},"isError":false,"timestamp":1784247581351}}
{"type":"message","id":"20d5fd64","parentId":"1de79615","timestamp":"2026-07-17T00:20:01.407Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Tracing connection sever timing**\n\n**Analyzing log mutex impact on QUIC stalls**\n\n**Confirming dispatcher spawns threads for streams**"},{"type":"thinking","thinking":"**Analyzing broker-client replay synchronization**\n\n**Investigating PTY stall in connection read loop**"},{"type":"toolCall","id":"call_tXXm34GAwGgMd7grYzOFrQL6|fc_0ceecce325b7fe2d016a59753080908198a2b7181bcd823492","name":"grep","arguments":{"i":"Tracing QUIC read pump locking","pattern":"\\.append\\(|read_chunk|accept_bi|StreamLog","path":"crates/spt-daemon/src/nethost.rs","selector":"600-950","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3195,"output":815,"cacheRead":69120,"cacheWrite":0,"totalTokens":73130,"reasoningTokens":741,"cost":{"input":0.015975,"output":0.02445,"cacheRead":0.03456,"cacheWrite":0,"total":0.074985}},"stopReason":"toolUse","timestamp":1784247581373,"responseId":"resp_0ceecce325b7fe2d016a59751d9abc8198ab6a15a1e87e779f","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a59751f6b2c8198865795cdff3226a2","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXUrnFTEk46tS_Eh71LGgFz9ITuIIQf2CfShxWJA3C0ZuA58tYhAevui72XmpcgVz3EAXx1F4K7KfeCKVzTm6Qe697aC6N2i5TsXFhRI_SZ3O_0yh_W8kM9MPoF7NpgX8OCTJ8wzKrmx83FRqvn8dZeAWUOnzzJwcQLvVZu392LPKBQhDLTl3w6WNMs6EigwslOdpUKsHop8B-qoK-n9K0epS0SNjW9EZpZ1Q60Xs4s0XKBW2_sRnMCdMZtHt8tLanhn3a7xaaSiJabtpiQZaqArMVvjQD65EmLrK8uXBZhuk3v7y6LnO1qxCgIMvP8e-feTqKHGTJbEFoFMrxpy4bTBbKgmRIUa5neZzTy0G7thip-3jLjoDR047CVq6Ibx5C-q5fkQFlsMXMrmy0vj8Yo8UAKf80pX7ISyyNO1xBSdUA9l3hjiw0ZPajFSZLDF9TXAV8ISX1xQrzOBpTtVZqLCALuRWvqAHiXqWbOxrmYIuwQEZAjBl5_JXej6qWWhcfxKOjmHUFFAcZDFVjyBXVaAou5V3x1I7TyNEvPuWKM5xN19obwfOm-k80UuE3FBQv4g1FJO60B80JG0t3kUyY6eCMTtvRPyyNTF6psgW1DVJACDC6PNLt6_9Q1LaCBiCxKm50lu5XnlfhWukOBQSGXKWb691qz6lYYvBTdmwgGw4sbvpVeColCe9PhMvPjRsH-yGntsgFqFgyjHkXqBJSCH3uIHt7Q_6uPKkfGpNgwB1cu4bS7r0uS2drS-a3Il04IKmvfR-rxG-VAYACHaITWi0OimKoCpG3YtZLWmdbOBZPC1c7I2L3897-9IcrtRYho_78iQIiVCKyhNAYUmEvMWRV5IzXFgQbo_V0tZhmCodmViFwL4D2I3DH_AS9Q76vyDCLFKTPHN_nzQoBadlGIgpYHgzZKHkjRwKSO-EqBUcBRzAGiISXMO6NMEPZdgQ-ZQYsnQopKSEWKE8kGQBQqgl-tA-XNw22tubBnAddJkmcfEaIW-NwtgVMKQ618PUsEtvlZmwUm1_K4qJU_HzRh5c4z7FpN4_Ig6foYZRoICFyJRzdku6lkH-9Lb3bc42Ded_ARyxIIbW19AZ7k9nKR-Y9Cl2f8qtK5HL21eys9zlXzVTqnGFX_pX9dzQ5lZTa_QGP4kXEq1Q3Ui1WXcwxAKKwc3PWM15-oo79GyXWJ_VV4NTv-fxoFfP0Ze5oyRwLt6SJcIOZjBPGqZFb7Cnnf6Tsd0f8EdWHDF009hwUxAGOh3f_bVYy0lrXZCVs3yKplntjofLTWd_FNbXVpqFNXKjGc2Xldtife-6DKzwUJ8b1oMd83Mz81dP5NMnrgQdRUrs807OX6S2czVM76SqKUW-XBDXWNIOQ0F6Wve9nVaCbYmtKBgs0xdFyP63kJy1Q3WzAl7C-AzVNFNbx1tYt4q9XbHmf3DFp0xRNOcX8qBZBqWVsW2Yae0eUGAaUGajVkORvYngjO93BA8VK3Hax8Iuz9ggYMWypvO8RjuWG67cnxi9Yir6l1lPPIWu8iFVX5RL2SkV69jRHiFf0SiAHLEy-R2IJNWO3pqqQX7tCO6uquxBqxQShXyzRhen0hgelmkTDd6Iuk1cq7ltLlWrnE6mSWuQPX-rmth6VMNIkVlvX8a0ijGvJN21kxr0E3hOgLiGR2wYig05sX58PzLAAI59F2zu4sqLrCxPXst7pBqhwtaSixIXgK861-pB4TRrqTVDlcQHCScnPwgSKT1HQvQvC10Dans97kGh9ys1f6WvMt_npgWnS_5Mb_AztRymjmlEKyPfIaoS_VHqEqsfvYUPxf7ReONHIEIzvyP4Sqex5FdPn9erPbMl7JkMh25nbLfOE1VUpRl270jLzwlhgxQBlxGrMBOiTCh1uiTJCTgr5rvvu3UlhnvfH3uK392BI8JGcTlRSoPxhcIWECdI1J0AJgtIZeNOUfN8G8rzettCyf-EVXQpyJZI0uFeN7aoB3ToU3R1vqCHm7OfCz9jmg4KdyvW9jyvgZWK3PVVfUohkVBhU3a_zihzOHCDS7aC5is9-dNtR1YBGvRt0RH6M9RjfUPoPkekhunUTGcy5DTYmasHgZj_4bNnlDRW6LkMW9MsePPfGusy97HlsROxTF4fnH-WUjl0N2Qmwk9W6r1oYol1Q7XlfE-7zEDzFOaCAQ6ppBuxnDyNB5qFitLhAlTDne8WutXxe28J1UZWvdd6rRBGJMyClLdvyddNA4DEPZnju3dt4Ji303C6ld18ijuPLAaxMiMysgBdsDwmUy9os1c2F_eeK47Hgx3fv9ofeOXPLl2JWQB_o3ZGiBLaJandE2vZ5xhbJNpXFv-Cb8MySCckkdWRwhNl8stUixQv6Sc_jxcG5PLt5R9H11oQwD71Q4freDtEisenIdx4PsVeQXxKggwBJf5_Jv8Vlc34HZUfyMBqcIwWDFGu0kbzPzXi3l9rSx-oSL9gWiGv1g0UlFxWW8MwoOHTWpnpdEcsbECCy0BQz_J4yZJaKynKf_5aAy6OZP1-a2I3ZpapzmCIcIcI4GTEp0FyxH1LFIfW_JWkluIq3RgRgGRfZL-qfaw5IZtVvi0SvBp-4tTFf_2ZeoRN1ZeZRwTcZ_KGqxm2bkB2-Q2l9pKdij0XviL6mLPmo3p8Yi7n5ayKSbohEiZbbAcgeBbciyVMk7Mm1qs_KC58XzUHJ4ntrJNF5dcsLqjk3JAqo8MsW5TNGDiOW6JNRIJXmy4xwjBheHmESjPQSCvCLqApm6vvlKXXIZx1t4R5p9CN_DMFcCcV403xxFo-CtkFt_RPBpViO6ntpKiAAy0y4I5v9gR2aOF9L2o6EpWxowYR1LvGiKoFpC3bEONmISioEKi7t5s8lFft3e08I9fS14wnp8hHWntmtLfy1Yw8s1u412ZzvAprTp9p2AwU7Kz60u-QV07qCsDKKOPmxoLOmjWDbnCSLIZy6Z4_WGA3xTR20Zs9TNFB3TRsBjjtvR51lq5kq2BBQT4VuLSBHvi5_0pK9ZApgslHMtZdcVtaEN7KT_vOslGpr3ZnmbPc2FHK8qQlrWzEbss6uMQqu0TPb6L9HaqpLyShKkghfl7AGMalT2bNyMsKL5jj83zPn2b5hKJI4YAl0n7vmCcfM3x76otpVoj7jsnMt6GZgCKA6guoQDhGkRQPC9jcGdDpF-QSDgAdyFvIfxaHyZprRluwD4ZMb--DCRqiTf7vdunHhnhBob7bbTIXYbsAHz-IWnk1Ecr7nsJGkAhzqNaoMZvheHV7WrJCY3FWkP_i-FusmMuuoPcojRvnqzC9RxEMbrk4L1nTDvmVDJ5bwgWZ-iYYl-dpANQWnLcFj5cc1Q1rcO-HEtLTIIgtS5cYJUAFiLIHxV9UntbfLwG-M4DdLQhsz60lLRcTHbrA87hvulKQWyesvLC7y6ZOxOpA7Oxic5YnnHszSDzE3oZwMA64fIvzQB3CtiKrd9OF_SI9hV7vsEnMxe_wwMNah0hT-vmImXDYtJohDkbRQB4VoyGViXPf11wuPutOozP3trjJBCi4DCHZg5Uxtpw3Bi6JC5EcQztghVy3YcsQiYHaBm2bJuL0JoAn7L08eHkkp7ObRaJyiCdOaGKrfrAgkkr9dQmGzqEecrSy8hrEdqyI2h8HPmysC8GccISTQqWLq8K-bP_M9nvH7DRHCMdUlBPb_kzlD1gQ1cQyPBH6WLJXE9xRSB4lymdgwcB0ELpk640pwJ54DgP6R7Lr4AXEItTMWQr3ybVL65PbBgGL0U95L7giUi1nwmmYycDK0Uorn_uFA5c8Tw3_Nvq0nfRtsbzC6n_DZ8zZkHOY7F83QjbOI2cXdxhQ6kauNfWbmidA4jGj19I4RKqnFB2Mk_BY-NZK7zG0Ap_yrGZ5aGDoz9hVuyzik29vhlS0K-0SMbGVxxJgUtKNIyH6dA2P1Mb0OUwfLSFFl6doyzGqxId6yttCWlHURllYwPwSsojFbHhAC4SQ4AILldZTZ2cPnCz3etdNRzkL_F8vBYzWCMGNlSzJDcvU9GXz8PtK-_VXz8lTvveSrKlHKX-hcrhY29Dh52IFFgQdISjkU6Vg6o48ha3152qffBFMvcotz4uADVTNHOZerdrzUSRyopWDZzZ4ADa67Sdwkhzv0wJKLjwYGac0ks8-ivpQOuzKIs1yHqKd3zKDfZ3t0Hx68bzhCFrsZI2PCFVfZDfm2nK7O3TFdBM92NPzhjfafRn-MtBTr1GUjYRa0c7jg==","summary":[{"type":"summary_text","text":"**Tracing connection sever timing**"},{"type":"summary_text","text":"**Analyzing log mutex impact on QUIC stalls**"},{"type":"summary_text","text":"**Confirming dispatcher spawns threads for streams**"}]},{"id":"rs_0ceecce325b7fe2d016a59752b8ff08198bea74baa8ba88d01","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXUwafMWpH91zoYVyt74P4BgmL6kmE2v08rOmcaKXzVRXG5di9WogxMQV_9Pw0Dey2LIR3B6mk2UklbLAH5nmZokKHbejkXdXtCmHfMcrSnmjvCy5PUBK3W9z6nj70a8nwTeKozjSVzEyX5e9zwPx41AX5AZxKKzgB3ensBRIqkOuRkC-bQtEKuUvocKnr8mTog7gI32TabALtO-PDPO0l4fgjE54-G_hVS6iq18aiGG4fa6NmIUZ9pzHbfjrdlVLY5ZW_8aDu4uipgXqmztZyA5aXUqC7F9QKBRHzFmot56E01nxiua4qxS4uxLH2_xpel7GNsxw7Fk3l18jq6AAlQ2tZvrgmf42gn3GmuyQiPYE4xxsiv9iS3dCVuMXuLlrGnbxWdUiJGUlNSQnJphfTPvCcejb9PXL25fBxqWWIAlmuo0c5o3LreZ3AdpFD196QD4uhtCRR_oZpXemWUXObN_bTVrbqusCwX0jvwz4_OrC-XlWLfx2BBXlfzjyAiZG0b7o1wQrlgekM8o4tlSdw2KA2I6Fs4xCTwfjJ-aU3obf_iMLD7x9gmwsZGD56lzD69RF1-wr0VAcfdvM9LuR4Q5kQV0RLYruwVPtbTZcQaXJNz9wLvDCduisHtuM9LFU_MuM_8D04FwkcwqyuVw909gp7-vu7z1Tkpv5td80zFE1PYT0HCtfXXYLT0iR_V2v93pHgqckBljVRidpg8xd9_xHIGguKOS6WtzyY6HQKGbKHfJpyLmoQZawPP8ufn3VsGHlLMKBQWsuveE6yDmPbb8otcSzrzfxrWUPcDPe5uVScACd6uIAQjM4BQdrKexBWKCdGFjfRi8P9GOJAb_u-QlOm2uK-77nPxbCtRgK9Qz3RoTHW8hf0gzqWDqaYdI6aM_bMbdLIhBBEMXpRBl1AxnL4fTxVQjNKNa11V_KR-RBuqWafeVsRGGREDDcAz06jK6T55h2FufzlwmoAdgHtfDv5LlO7WQrXUI5uOvc0f0D1GG61dAq7qI9Adt-dDCXIJ1yfGcwvXv4FeROpbhi-CfZtiesRzGBzQU_Yc9sIzbFkiQ6XQ1toNKCLEuknGoDFd2JgfXulvZL86XFDqDkm8GFewvMCDtOJ5f71Go8IfEtgqG0rIZ5BlTpR998JFP0UQtbVPlveJ18Hpgl23FgauF7ZjJkyMNn2m_yzeR8rN5SMzmS0PszgV_C2eqVbmXKQAL9BpnVuSZm8kCG8Qy2lrzP1vy_MSGLSY78c_FtbxD0N-PZFAH5cvOL1U4pU9m3Bi0lT-0jNR6rRx8aiUP7tOPsMv13zDBKPsveenaaopWnN3fQIkulX-uOp0ITDjRRdoFtQTMV7BY1Ab8Cif8DfcxZx3GeXBe9ZcZLIpAX16eCeuQZequS6K5QYxZclYUuf7_UchsKH2bCi2v-cU2QRiwoCjP9HD_jws3OgeiP0l5l_TXDMtXFbAtR2KByc00ivrntlYZvHjX2700OCkpSzuU_5kolNZe8sIJpmH-lmXwmAxkGo5EkaVVFUsU4ne3oq-rThZ_MKfnkNGg6_gSXqOZxWJ2CdUIdn2QV-m-B7qA4q8-sj0cxNFOiBYPmbWX1iXLUf8l5Ra7LCmvHZ_5g6BQK0wsS0vcTjxqNi2mvOlWMJbHB-8M5Pb-RkGlpBD6rzFZ-bfTmXpjUkPvJN5Ek4yWo46c8rJ-SbX5zATmmvnwrLZKK538rOH5-9mzCNvTcuLvnrHDLcC4bed9GVDkq2OdRjShFNoA9oR-BwOS7lxboYfpMVaFIqkHAV8uBfLxPHx_soh32oul0yPlFrceJkVvoNJ89iQuiiCNHI5LDf34yaMIhbE9cF4ic9UBMcEtIjrqD9kOZ2gz2LfpzOfpZ0dOyrtUNfonXzgWwzx7PhhvZ7uwE4-MmkNh6373k3WaKImPTje1Seq6LGdM38aSfWvYZCGWceZ8w81JEuYTERUO74EZx4qsRZmiIC9u0BR1YRFA_zmBkXlUj89qznYw5m2qOb0UsyMJfBOgwvF6yMyVZxnrpaGMl97ZUlgHjfJdstVLHaRNe7JUTQVbXVN5XSJwPWgtkM1eUyk85CiAMa3DVosdoorTotyTxrxiwq4U08D5SUsPhK2NzfJgUTIk544oJgVszrfRCOYQGSqdZAn_m85Kn2-ZjT2RQ8ZvrPgIRbu4jgvvlmqYsaPo3VuCOvd02Xcj16--YCJXRoCUCFHmJkS-i_ppvcf3v1qLi8avbHgZpPjrcxkaI7Yjw9PVW38M65a0hWSSj2yLwx5sJRpH9unqFi7MT3-vuoFXDM65YAK6HEYKr6SOLwLF81yZOEMnZg==","summary":[{"type":"summary_text","text":"**Analyzing broker-client replay synchronization**"},{"type":"summary_text","text":"**Investigating PTY stall in connection read loop**"}]},{"id":"fc_0ceecce325b7fe2d016a59753080908198a2b7181bcd823492","type":"function_call","status":"completed","arguments":"{\"i\":\"Tracing QUIC read pump locking\",\"pattern\":\"\\\\.append\\\\(|read_chunk|accept_bi|StreamLog\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"600-950\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_tXXm34GAwGgMd7grYzOFrQL6","name":"grep"}]},"duration":20013.5196999982,"ttft":2059.619500003755,"contextSnapshot":{"promptTokens":72315,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_tXXm34GAwGgMd7grYzOFrQL6|fc_0ceecce325b7fe2d016a59753080908198a2b7181bcd823492","toolName":"grep","startedAt":"2026-07-17T00:20:01.408Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Tracing QUIC read pump locking"},"id":"d17b55cb","parentId":"20d5fd64","timestamp":"2026-07-17T00:20:01.408Z"}
{"type":"message","id":"f1ef128b","parentId":"d17b55cb","timestamp":"2026-07-17T00:20:01.412Z","message":{"role":"toolResult","toolCallId":"call_tXXm34GAwGgMd7grYzOFrQL6|fc_0ceecce325b7fe2d016a59753080908198a2b7181bcd823492","toolName":"grep","content":[{"type":"text","text":" 717|            .unwrap()\n*718|            .append(PRESENCE_DISCONNECTED, id, &hex_close);\n 719|    });\n 720|\n 721|    // Inbound-stream acceptor: every peer-opened bidi stream joins the table\n...\n 726|    tokio::spawn(async move {\n*727|        while let Ok((send, recv)) = conn.accept_bi().await {\n 728|            register_stream(\n 729|                &sh,\n 730|                id,\n...\n 754|    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);\n*755|    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));\n 756|    let room = Arc::new(tokio::sync::Notify::new());\n 757|    shared.streams.lock().unwrap().insert(\n 758|        id,\n...\n 773|    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the\n*774|    // same `StreamLog`, so nothing above the pump can tell them apart.\n 775|    //\n 776|    // Backpressure (M11-W3, REQ-SHELL-4): a retentive (tunnel) log never evicts,\n 777|    // so before pulling the next chunk the pump WAITS while the undrained ring is\n...\n 785|                await_ring_room(&log, &room).await;\n*786|                match recv.read_chunk(STREAM_READ_CHUNK).await {\n*787|                    Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),\n 788|                    Ok(None) | Err(_) => {\n 789|                        log.lock().unwrap().finish();\n 790|                        break;\n...\n 802|                        }\n*803|                        Ok(n) => log.lock().unwrap().append(&buf[..n]),\n 804|                    }\n 805|                }\n 806|            }\n...\n 816|// [impl->REQ-SHELL-4]\n*817|async fn await_ring_room(log: &Arc<Mutex<StreamLog>>, room: &tokio::sync::Notify) {\n 818|    loop {\n 819|        {\n 820|            let l = log.lock().unwrap();"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/nethost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":8,"fileCount":1,"files":["crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":8}],"truncated":false,"displayContent":" 717│            .unwrap()\n*718│            .append(PRESENCE_DISCONNECTED, id, &hex_close);\n 719│    });\n 720│\n 721│    // Inbound-stream acceptor: every peer-opened bidi stream joins the table\n    │...\n 726│    tokio::spawn(async move {\n*727│        while let Ok((send, recv)) = conn.accept_bi().await {\n 728│            register_stream(\n 729│                &sh,\n 730│                id,\n    │...\n 754│    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);\n*755│    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));\n 756│    let room = Arc::new(tokio::sync::Notify::new());\n 757│    shared.streams.lock().unwrap().insert(\n 758│        id,\n    │...\n 773│    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the\n*774│    // same `StreamLog`, so nothing above the pump can tell them apart.\n 775│    //\n 776│    // Backpressure (M11-W3, REQ-SHELL-4): a retentive (tunnel) log never evicts,\n 777│    // so before pulling the next chunk the pump WAITS while the undrained ring is\n    │...\n 785│                await_ring_room(&log, &room).await;\n*786│                match recv.read_chunk(STREAM_READ_CHUNK).await {\n*787│                    Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),\n 788│                    Ok(None) | Err(_) => {\n 789│                        log.lock().unwrap().finish();\n 790│                        break;\n    │...\n 802│                        }\n*803│                        Ok(n) => log.lock().unwrap().append(&buf[..n]),\n 804│                    }\n 805│                }\n 806│            }\n    │...\n 816│// [impl->REQ-SHELL-4]\n*817│async fn await_ring_room(log: &Arc<Mutex<StreamLog>>, room: &tokio::sync::Notify) {\n 818│    loop {\n 819│        {\n 820│            let l = log.lock().unwrap();"},"isError":false,"timestamp":1784247601412}}
{"type":"custom_message","customType":"irc:incoming","content":"<irc>\nIncoming IRC message from agent `V034DiffRca`:\n\nField correlation sharpens W1 risk: ad7fde8 retry claims turn pre-v34 one-shot abandon into repeat reattach/replay. Each Failed attempt requeues at 500ms then 1s (max3); each Attach attempt installs stream subscriber and synchronous ring replay. Current trace: 33 exact-15s subscriber poisons, 4,361 attaches, min poison interval 29.7s. So v0.34 can convert permanent loss into recurrent 15–30s transient stalls. Also `first_line` falls back on ANY opener RPC Err, not just unsupported-old-broker: potential 15s opener failure + 15s subscriber fallback ≈30s. Fully v0.34 doesn’t exonerate this; retry/pinned-seed are v0.34 behavior.\n\nAn agent sent this while you were waiting or working. Any active interruptible wait was stopped early so you can read it now.\n\nIf a response is expected, reply with the `irc` tool (`op: \"send\"`, `to: \"V034DiffRca\"`) — you may finish your current step first. Nobody replies on your behalf.\n</irc>","display":true,"details":{"id":"1532a9be6c9c5f27","from":"V034DiffRca","message":"Field correlation sharpens W1 risk: ad7fde8 retry claims turn pre-v34 one-shot abandon into repeat reattach/replay. Each Failed attempt requeues at 500ms then 1s (max3); each Attach attempt installs stream subscriber and synchronous ring replay. Current trace: 33 exact-15s subscriber poisons, 4,361 attaches, min poison interval 29.7s. So v0.34 can convert permanent loss into recurrent 15–30s transient stalls. Also `first_line` falls back on ANY opener RPC Err, not just unsupported-old-broker: potential 15s opener failure + 15s subscriber fallback ≈30s. Fully v0.34 doesn’t exonerate this; retry/pinned-seed are v0.34 behavior."},"attribution":"agent","id":"eb8ee2b5","parentId":"f1ef128b","timestamp":"2026-07-17T00:20:01.413Z"}
{"type":"message","id":"55a73cde","parentId":"eb8ee2b5","timestamp":"2026-07-17T00:20:17.513Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Inspecting worker sequencing and retry behavior**\n\n**Analyzing stream attach and operator input handling**\n\n**Tracing stream-subscriber role and input stalling**"},{"type":"thinking","thinking":"**Inspecting PTY attach errors**"},{"type":"toolCall","id":"call_2BRLmsOqrjzO0LMm5VePL6RI|fc_0ceecce325b7fe2d016a5975410200819880d2e31b2489f61a","name":"read","arguments":{"i":"Reading attach dual-connection flow","path":"crates/spt-daemon/src/attach.rs","selector":"280-440"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4765,"output":634,"cacheRead":69120,"cacheWrite":0,"totalTokens":74519,"reasoningTokens":591,"cost":{"input":0.023825000000000002,"output":0.019020000000000002,"cacheRead":0.03456,"cacheWrite":0,"total":0.077405}},"stopReason":"toolUse","timestamp":1784247601421,"responseId":"resp_0ceecce325b7fe2d016a597531cb108198a5b8edae15c6f077","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a597532e0088198879f20332c7c8eb8","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXU_l1oxpLixCE3ZByi5NDgq4DC-hzPcEcAMSJG5jGAVRz4ColsgXM5w20lWHawCrPhKFCfFdXH8SsMtFLxvpLImE8LF02Gqj3BkxIZwrf7-0XnGkr6ckc6y6EoVkt-mw1cVRlQb_8-XLM6ujZrleTVwjH3gzmuyfj8AB_Zz0KvFZ_XcXvoIC3Htf7SSmwLJ0RiUBnHj6S5IVwFf1XX4_KKu7_wHmmAZ2BULst8ARIRlRpJZdANNv-Uzd0nNezfl-FCsIN9kmdolJwHVy7wpjjdokArf91ijf_OUHdPpzwK-Qbk4KRc6-DDJ8hEAAhjuk5N40Lzp9h4kE9R84LNXRXb8hf9TfdQQBe9TFsV2fqHZHAGHE21awfVi0NHxGb5ES5a04qTefRLcdV25sbe6hTCiist1QTS0Y0420yF8xff7NCxr2x8xtkrw9IkoTIrIxk_u9zQRqMD6slnJqjWTWXrBuKLa7IkLXZt2IJBZwfc8rb62u2692bWTZr68LDUwCKzJ1OP7W_xg76nu1WP-XBQC4Qc2UvUu5tX49S2yZSGF6UeYTwHv9csi5JXSUkPeizsBgMZ4mSnPZoTjW95ZYVg2cFYxexzsulxsU9IPyEHtkfeK2EDBD-OY8EKncz4mgRffMXVCS9f_uA-6VJ6ZgifKtOYFM4p0-O8ILpxDvTd83oX4s57kXQMkTmM02i-EAYIUujv82BTQi-Mo-URqefDv1E3_4DsEextyHf6jdB9rx5oSzbJWQ2WzF6obKrW32ZK7hunZoReVvCaM-V7LuuNI19KXebDV7VJfCDri67-yQXZYeQf7RKTnnlV8i3xIBOiP5oNTFENJej-asn05Dc0AmCakvqUtjD8g2hwmVJGSKzwUnrybcmOmzZiqY7QbY3FYwY4G-WwM_h6oGN6qM8zqNGlyToYsLgb8kCE4Z_cQZaQcFJEaVHbGKTF1TBCHmqjGDOofscqHv0nP05Qc7b-dWkfT-2AlP6p1czpk470bAI8G6N9Vs_3Sd6np1fDaDlNdOAnpVIMMZ95wBN-pDjD59-q47BJgrMoqrpcXxwi-hSDtYLsT27FKFoCR87D5xbJzZpLyTkHy4SlQmGn9QFejEOgsbfcmg3019bZueIkNBz4Vmjni6WHf5XwfE6mk0mQwGUhQDFDDELbR7C7AnOjxzqTaUbK7S2GhLbI6gYsRlbEhZQMGLb2C1fb6pgFVOBLpOLnsCMufoPQks330H78aPI8l0Ew0B3W2wDokPx4QLEPP4-Wjh6KUlxpMeo1BkgHGV0frnhiXpOFN3v6tAkJn-fSi5UU8x_81a9kPyEBoyHu4SLWFnWiLxjkn1o6L1c1hrA4L__SdCXfW08QCKujb8KycQMSoNmzDhHvVcO2mTnrrQiTPr19ufny3iA2i-TR4nS73kUbsqhDQMA8nVVCLKoSOqRdjS9zFTdGyqSnFsL2kbKEa-FoHP8rm3j4K7Y6ctHOSSsaO7AVNk7PWQGFoScu-cg5RSkNRfArwbTQU5aKFHZc7IRzo6aa5kaIjg3YG8aT65krMOUwDok-fuJma9CiiwVKh8CvYfKUkHwmZ-LoE6woY1IzpGZTOr59wazCfB3AsntnSkYiS_m_46mifGM-NNpNdR9-w7OHNTkL0uSSc5KfjE2WO7iv9cyvkE2D2tlud2nU0oUfeSRFRvTtTsc2H2w-jyg6pBpiEwuRvPfuAtaA0ACUqZ9RRdqFSHW9Rqw1mSTQX8vE1KjLyJBghj_0S5mQMb5fWiSrn-G99Ccmv86nD1HrUKJbYSIcjsFftC5tVCKBa-04F_TW7jeT9reDJ1XAM8hFPHgMnsNUj4W506V-LQpjwkhmOa2eXheMuVFoTwouaEx4oGTR6DrYI9HwXT2AtgTdxTt82l0Nn7kz9yAdtel-EpveiOC2pJFe9-oEuTCYvYDNwVnVyUvFqfCGiyREoLQvOQp7lZtHnWsUxnOMRGfIU0S1XGhb5cHovm-BP5M7jTydLiJ2PKudxIe2xPhQb0JriHzxu9LeFObE2_v_RTlmx7KquAlx1RYqeUBffv8YBhxv2r7K88hfIuu6R9a2iUHijOq0rt_LSAgq9NGdsht5OEiecGoDV4NZHdRtefdo-K8dwwT4KJMcE9nrkkA6xaQER1KOW1JYgDreqUln_uE71hsdNrKit_9r-l8_9-p00UmJyWSqO6qF0-xZX7BXNn_MbVqT0IBGE7Pq4WsaKD80zsct6VctN3i5UU6p1ufb-gBvNOG_h3cs7iHy_8cZ4C88fcuzj1w7tOFX-VyQnNK2ZNVfgTu-HfrWnqWw1RlmZKFyOPJiPiLa0gpA5HMu7drB5DKOx17ePFooZ9Y1veWpfA5ZFcK7SAnvhyKgJIqmOUnGiiU_3tLlGM2-QWOHDYaYUGSAJfcd2UdkGKa3vmhHqyuIDSVP250P1-ppdmxmUtH7eYx0SKGOCIIBB-8q1R4UlTZf01We_j6VCCpElbREm1dNcW5Eiws0IpwwvKWPcO1sn3YnVTr_Ta3j6HJluZUOIJLtxmOJiUQpx3Gl96FAJ45mNrmSVxCwHm_8umvCS7THLyViWjTwhtuOkeJsLq7aeTU_3x6ef-lwkLu_cEuMXyF9SLL71DgxaUSkT-Vx6lAdMBXUsmyROFUDitJF8eBvhQAIEvlOsfmBMlUSF7yzZCZQfYRYFm30Gi2WYFRVrEv1fnYHpmn3Hxewe5r-QZX1_8oQ6Py_MdbGtlcEtWv3JRKKgSxI3u3XiqO59JjGsRgXn2N0wWse0Qt6lKHjBMGqlDt-vDPl11KQeoQfdghXIkWMfwNeQM0LWhmm--tj0W59N_6LmnEFaEgiK4wHdrixjiOw0yPKn_XHp0hM43PF7GGwg49fBbUJzdc3PkEw6FsgVuGFNBBA6dieJL4lkcXwqD8d1aorWP_7AulXJjQAHpStVDe2zc7JTn60eb13yCtQkPNFpkoV-P6VJ4wIVPoFwGSS4SH4dX-LGdwNZ_xep7LnJXD5daOZYcXVrarVKdjW4w_K1JiYDLdFGipT2FlvcmxJAFIhpDIoSUZCDgIJtdciGA5vM1rGfmGWmw1gMU8cA69L0uYFHpuMPB7b7tk8__Yjqe-LCP966HW7etucGPOXfbXBfvae2mBry5aIHxVzj8m860tvCJmQ_ls1nzXOHIQu7-E5yJaTvF3MZoDnj1rGBH5j6HIH4UPQASdcHJJhR5gUdUr_r0JIrYrKtO5LSdIbm0eihW6uKndK3BMcghHfW5bD-W94QOLtXwjVqyCfZD0mZ9wLHPoO83XnLPhBkJUWXW44NpNyCmQe1G-OG8XsdlRF0xIptrKgZC1LQcLn0rUQoVl9Z74wWQXRFsliUqhPSddQxvFO2VPR544dhgP7tCoRuENBjuXzANOPLFGyoyD-EDkDzMRfVfB8FYxdzkmmLBFN19M9qSJTOz5MipEMsUMxZi_shHWxrWycw1qDfKejTTpnq72R0fp7YivpYMbf5qfAfuIc0pz4Dbj64cR2wg29ef_6Qwx7GP5rapl3lgvw1tRMGA65h449F7_7u-GJGbf0OOIKAxusRn0yu-4qF_CuV2NfNJCLkafSqyi0HljUusmiuDEocpd0THrbPBWe6MsNzP4TQ9QezkHaL-59iYqEptHYTt8sLASPicZ0J38Nizjl70La2QfZppFvTa1OHMrl3YrgXwIZnMQZgCpkCPEw3Faoh8Sw3mSS3_KeIPdKr8oHnbAHc0ov2PKoqszgqGEtuNKO32VYEwsHbhDMiLLBCQVepPEOoC0TLD8w58JbPtQO5y-fFz0YK6M76MpB3njXEqrsqJAEdKFIZan6dW1pNJQx4I9sgzcdN6zhe0t5uDc9XSbyixWrar6xHbstiw1tuHv7R0ZmrqwaGd5IO9G5ZwQKzysPQaB1bG2p4SKY67C-Ydr11ccO-tCKjbtWqzABaqD-1hQ23N1E3NXDtdFRghamSJJCWpVQcY_I2th4Wr2K-Ocxg9MkrKuK5lE5rb1Ri_W95r3A6JiWqNyGyZbhW9cEjNH3e-nhgl5RCLJkuSjtaISyTUmPsgzyRS4P0Es-EcTaCXeHv6An8fiGbtI1EtimrEjNSxEKoO2cBEtQw7Opg2dS-O7FY9dQAjpYTSOo-4936FUY-YDknYlLylANHDq7DJd2-tZ7WiAR3LQPg_1mg-CrQQhITOWmSi9j8ETO5MUWPAMsokavSxIpIQWmW4dD5ljvc0ZqeFn5nkRfNOaVv6-mrnjol86QGTO2cxmCsFug6Uf0Ri0xYe0v35oQlU3JzFQJDvxlRqT8LfzfCt5vFAZzoVRkde5Le_5srCaE1VcZReliN9eHvknktRsx7OxYzOCbpdmw6AL0gAQrWTQ-_8SJP2RJ-2-DE80ljShe9lgNMmfw8Y_WuKPrA2t2vjgm0CyIoiolCdN-3Wge7FpdHgVxb3TgySWYdOopxmeRIzC8lShryucvqgimlDvukxVZi","summary":[{"type":"summary_text","text":"**Inspecting worker sequencing and retry behavior**"},{"type":"summary_text","text":"**Analyzing stream attach and operator input handling**"},{"type":"summary_text","text":"**Tracing stream-subscriber role and input stalling**"}]},{"id":"rs_0ceecce325b7fe2d016a59753f6d7c819886aa651cceabf302","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXVBBgt_Q3WBtty4PDBEHGfUDDsxGGBnPGpIOkfV4ONGaLTyerVyl-eunFLKt7IzH0_XkiA9GccTHzWVOe02did1g2QECWwkBZbFG_JTL3k-WGcH8xsjcKYFgaPAi9t7mqfih9Fxyb_J8sB1qpM8oAOeN_Mn-WAlVz3gA69ZaTuA1pTPiGtOaxjCYlPSoMU8Irp5_JP1Vf4v1K9e4jpd6Ql3AhhNiQ2JXfGjUoVA5wuZG-66ep12P76mN9dKBIltiD_xczS1Q58yImNpJaHSevy3OUkTIuKR0hzV8qpn6QV39ALORGpghJdIOY3JtYjIchslXDpBIoojzJoW81PSltT4mHQrGkm6ryO4xZ8-O7USpz0-tcxAQuZqxzTxvIfkyk6skiIN2Cn0jAobDCA1LSHnouykgh5nB3CDtNbZzy-vCjNUiyQnztfZig00HvfDyIaccg0m44HhP9pPF_1LO1zIzx0hGaIla6d6xtYAwPpHJ3ngNtqu_mR0GB0usA-56-52x1PLctrC2pzzjfhm5Bo_-zER0J2HePs1Dj36BM6HXEP7RjfkHX0Dxd_qq58hPYh1Dx-BPRh6-MayQqBWA6V8cMKRfpl_zT0xBPC3773loOiu3rQqCBb8uENsAEErlPKKdJkpactzeELwbdtLt3s40pl8nMrojmx3QV9mJyyoGV_jw7XqI9ZuPZnUYfjVfXSk2z3gGuOXPjOiqjKzu3iDTJxuHvfHl4F6eDzYKf3v72kNsq9DrasitsFCf5Td2QYR2qpBUlqkyZFJqGBk-58ixMH_UTn2gz3gdfNCzBMkvjDqzOWtQ66Q0Z9kaJLitkmFTpW6YDBGlOFgICfycwGRT-q_8uA8cmHeG7WKVhPlMt9g23qmLyG5z3iKv07q0TkgDDmagQXIjkhzLGO6PtiZZSxhn2gCKA-KWvSFf39aySMvTOi86wKVzockDT0g18_2Pv3Q4_o_qsjzPllzqkBrnS7Wjelo4siwg0vVWb-g9rXCTZc9VWBrXyBGShhcWh4XNK8duIXDExa8LYfoTgYx3oTF0QlxW1O2k7tUySFZCE01NP3CBVgrSftwbPXq021aD7aWpZAflH6ufVUCaE9Drtdd3IWkdRsCwPxCuO3jGOCfeljm9zAHkwuVLflVzNtDySy-3U43P3LLPY_M-CQwjswpeijsgbUF3IzNUStVElzXqV1hl3G4r0Oloem2nWLX3XtHygDi98s--eG5c0o2LTUnlrQLI3lCMU6HXrlAL-NLO0-0FylSW3ZF0kuRSOPOE5__zFDauzWYVefDTnU86K2gFpgFiGl5JggngU_901_ubLi0lCpHNSDPHCxHjGEhWJjlW88U2TtTrG5DA5nQMgtBoDcfDuyThqh6ZTHSsAdrKqSlz8wX_pagwIxdxeGV","summary":[{"type":"summary_text","text":"**Inspecting PTY attach errors**"}]},{"id":"fc_0ceecce325b7fe2d016a5975410200819880d2e31b2489f61a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading attach dual-connection flow\",\"path\":\"crates/spt-daemon/src/attach.rs\",\"selector\":\"280-440\"}","call_id":"call_2BRLmsOqrjzO0LMm5VePL6RI","name":"read"}]},"duration":16087.78140000254,"ttft":1443.6464000046253,"contextSnapshot":{"promptTokens":73885,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_2BRLmsOqrjzO0LMm5VePL6RI|fc_0ceecce325b7fe2d016a5975410200819880d2e31b2489f61a","toolName":"read","startedAt":"2026-07-17T00:20:17.513Z","args":{"path":"crates/spt-daemon/src/attach.rs"},"intent":"Reading attach dual-connection flow"},"id":"b104e60c","parentId":"55a73cde","timestamp":"2026-07-17T00:20:17.513Z"}
{"type":"message","id":"a14c9f0f","parentId":"b104e60c","timestamp":"2026-07-17T00:20:17.518Z","message":{"role":"toolResult","toolCallId":"call_2BRLmsOqrjzO0LMm5VePL6RI|fc_0ceecce325b7fe2d016a5975410200819880d2e31b2489f61a","toolName":"read","content":[{"type":"text","text":"///\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\n/// record — a reconstruction that waited for one would idle forever. The\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\n/// wire-arriving Request (same gate, same subscribe); a Request that then\n/// replays from the ring anyway re-runs the same-identity re-take, which is\n/// silent and idempotent (the successor-re-take parity the fix must keep).\n// [impl->REQ-INST-8]\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\n// [impl->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\n// [impl->REQ-STREAM-OPENER-DURABLE]\n#[allow(clippy::too_many_arguments)]\npub fn serve_attach(\n    brain: &mut Brain,\n    broker_name: &str,\n    stream_id: u64,\n    endpoint: &str,\n    origin_node: &str,\n    net_from_seq: u64,\n    remote_session: Option<u64>,\n    seed_request: Option<AttachRecord>,\n) -> io::Result<AttachServeOutcome> {\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\n    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\n    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\n    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\n    // session output (broker→serve) — including the controller's synchronous,\n    // inline, multi-frame ring replay (`become_controller`), which holds the\n    // broker's single-threaded per-conn handler for the whole replay. If the\n    // forwards rode `brain` too, that stuck handler could not read them →\n    // serve→broker backs up while broker→serve replay backs up → mutual IPC\n    // backpressure DEADLOCK, and the operator receives ZERO bytes (the bug: an\n    // attach to an ALREADY-PRODUCING endpoint-run harness, non-empty ring). The\n    // two directions on independent single-threaded handlers cannot block each\n    // other. `send_stream` is stream_id-keyed (conn-agnostic), so forwarding from\n    // a second conn is correct. Order is preserved: this one serve thread reads\n    // every Output in seq order on `brain` and forwards it in that same order on\n    // `wire`. (DEEPER ROOT, post-v0.12.1 follow-up: the CONTROLLER replay is a\n    // SYNCHRONOUS inline write, while VIEWERS use the async dedicated-writer-\n    // thread + bounded-channel pattern (`add_viewer`/`viewer_writer`); unifying\n    // the controller onto that async path would remove the inline-replay block at\n    // the source, but it touches the authoritative cursor-advancing path.)\n    let mut wire = Brain::cold_start(broker_name, now_ms())?;\n    let mut decoder = AttachDecoder::new();\n    let mut attached = false;\n    // The Request's intent — gates wire input (a viewer's keystrokes never reach\n    // the PTY) independent of the broker's later role reply (no IPC-ordering\n    // race on the input gate).\n    let mut intent = AttachIntent::Control;\n    // The broker-confirmed role (arrives as `Subscribed`). `None` until then:\n    // controller input that races ahead of the reply is BUFFERED, not dropped\n    // (the replayed-input-after-restart case) and not leaked to the PTY before\n    // we know we control the session (the busy-refuse case).\n    let mut role: Option<ServeRole> = None;\n    let mut pending_input: Vec<(u64, Vec<u8>)> = Vec::new();\n    // The attached session — captured from the Request so the terminal paths can\n    // explicitly release this worker's role (clearing driven_by / viewer_count\n    // promptly, not only on connection drop).\n    let mut attached_session: Option<u64> = None;\n    // Last skip-to-live re-subscribe (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT): bounds the\n    // evict→resubscribe rate so a hopelessly-behind viewer under flood sees\n    // intermittent live bursts, never a CPU spin.\n    let mut last_resubscribe: Option<Instant> = None;\n\n    // Durable-opener seed (ADR-0038 B): process the dispatcher-pinned Request\n    // BEFORE the event loop, through the exact wire-Request path — a rolled\n    // ring can no longer replay it. Non-Request seeds are ignored (the opener\n    // of an attach stream is always its Request).\n    if let Some(AttachRecord::Request {\n        session_id,\n        from_seq,\n        intent: req_intent,\n        endpoint_id: _,\n    }) = seed_request\n    {\n        if let Some(out) = handle_attach_request(\n            brain,\n            &mut wire,\n            stream_id,\n            endpoint,\n            origin_node,\n            remote_session,\n            session_id,\n            from_seq,\n            req_intent,\n            &mut intent,\n            &mut attached_session,\n            &mut attached,\n        )? {\n            return Ok(out);\n        }\n    }\n\n    loop {\n        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\n        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\n        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\n        // snap-above armed at attach, REQ-HAZARD-VIEWER-RING-ROLL-SNAP). Before a\n        // role resolves no Output flows, so a plain read is correct then.\n        // [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n        let event = match (role, attached_session) {\n            (Some(ServeRole::Controller), Some(sid)) => {\n                match read_controller_event_resuming(brain, sid, Some(origin_node)) {\n                    Ok(ev) => ev,\n                    Err(e)\n                        if e.get_ref()\n                            .is_some_and(|inner| inner.is::<ControllerIrrecoverablyBehind>()) =>\n                    {\n                        // Irrecoverable: the dropped frames rolled out of the ring.\n                        // SURFACE a marked truncation to the operator (never a\n                        // silent skip = B2 lie, never a spin), then end cleanly.\n                        // Graceful continuation is deferred\n                        // (REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND).\n                        let notice =\n                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\n                        let line = ndjson::encode_line(&AttachRecord::Output {\n                            seq: brain.controller_resume_floor(),\n                            data_b64: encode_bytes(notice),\n                        });\n                        let _ = wire.net_stream_send(stream_id, &line, None, true);\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                    Err(e) => {\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\n                        return Err(e);\n                    }\n                }\n            }\n            _ => brain.read_event()?,\n        };\n        match event {\n            // ── Wire → session: requests, input, resize. ───────────────────\n            BrokerEvent::NetStreamData {\n                stream_id: sid,\n                bytes,\n                ..\n            } if sid == stream_id => {\n                for rec in decoder.push(&bytes) {\n                    match rec {\n                        AttachRecord::Request {\n                            session_id,\n                            from_seq,\n                            intent: req_intent,\n                            // The dispatcher already resolved endpoint_id→session\n                            // (passed as remote_session) BEFORE this loop; ignore\n                            // the wire copy here (a mid-loop re-resolve would swallow\n                            // interleaved attach records — see remote_session docs).\n                            endpoint_id: _,\n                        } => {\n                            if let Some(out) = handle_attach_request(\n                                brain,\n                                &mut wire,\n                                stream_id,\n                                endpoint,\n                                origin_node,\n                                remote_session,\n                                session_id,\n                                from_seq,\n                                req_intent,\n                                &mut intent,\n                                &mut attached_session,\n                                &mut attached,\n                            )? {\n…\n                            }\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}\n\n[Showing lines 279-443 of 692. Use :444 to continue]"}],"details":{"truncation":{"content":"///\r\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\r\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\r\n/// record — a reconstruction that waited for one would idle forever. The\r\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\r\n/// wire-arriving Request (same gate, same subscribe); a Request that then\r\n/// replays from the ring anyway re-runs the same-identity re-take, which is\r\n/// silent and idempotent (the successor-re-take parity the fix must keep).\r\n// [impl->REQ-INST-8]\r\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\r\n// [impl->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]\r\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\r\n// [impl->REQ-STREAM-OPENER-DURABLE]\r\n#[allow(clippy::too_many_arguments)]\r\npub fn serve_attach(\r\n    brain: &mut Brain,\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    endpoint: &str,\r\n    origin_node: &str,\r\n    net_from_seq: u64,\r\n    remote_session: Option<u64>,\r\n    seed_request: Option<AttachRecord>,\r\n) -> io::Result<AttachServeOutcome> {\r\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\r\n    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\r\n    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\r\n    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\r\n    // session output (broker→serve) — including the controller's synchronous,\r\n    // inline, multi-frame ring replay (`become_controller`), which holds the\r\n    // broker's single-threaded per-conn handler for the whole replay. If the\r\n    // forwards rode `brain` too, that stuck handler could not read them →\r\n    // serve→broker backs up while broker→serve replay backs up → mutual IPC\r\n    // backpressure DEADLOCK, and the operator receives ZERO bytes (the bug: an\r\n    // attach to an ALREADY-PRODUCING endpoint-run harness, non-empty ring). The\r\n    // two directions on independent single-threaded handlers cannot block each\r\n    // other. `send_stream` is stream_id-keyed (conn-agnostic), so forwarding from\r\n    // a second conn is correct. Order is preserved: this one serve thread reads\r\n    // every Output in seq order on `brain` and forwards it in that same order on\r\n    // `wire`. (DEEPER ROOT, post-v0.12.1 follow-up: the CONTROLLER replay is a\r\n    // SYNCHRONOUS inline write, while VIEWERS use the async dedicated-writer-\r\n    // thread + bounded-channel pattern (`add_viewer`/`viewer_writer`); unifying\r\n    // the controller onto that async path would remove the inline-replay block at\r\n    // the source, but it touches the authoritative cursor-advancing path.)\r\n    let mut wire = Brain::cold_start(broker_name, now_ms())?;\r\n    let mut decoder = AttachDecoder::new();\r\n    let mut attached = false;\r\n    // The Request's intent — gates wire input (a viewer's keystrokes never reach\r\n    // the PTY) independent of the broker's later role reply (no IPC-ordering\r\n    // race on the input gate).\r\n    let mut intent = AttachIntent::Control;\r\n    // The broker-confirmed role (arrives as `Subscribed`). `None` until then:\r\n    // controller input that races ahead of the reply is BUFFERED, not dropped\r\n    // (the replayed-input-after-restart case) and not leaked to the PTY before\r\n    // we know we control the session (the busy-refuse case).\r\n    let mut role: Option<ServeRole> = None;\r\n    let mut pending_input: Vec<(u64, Vec<u8>)> = Vec::new();\r\n    // The attached session — captured from the Request so the terminal paths can\r\n    // explicitly release this worker's role (clearing driven_by / viewer_count\r\n    // promptly, not only on connection drop).\r\n    let mut attached_session: Option<u64> = None;\r\n    // Last skip-to-live re-subscribe (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT): bounds the\r\n    // evict→resubscribe rate so a hopelessly-behind viewer under flood sees\r\n    // intermittent live bursts, never a CPU spin.\r\n    let mut last_resubscribe: Option<Instant> = None;\r\n\r\n    // Durable-opener seed (ADR-0038 B): process the dispatcher-pinned Request\r\n    // BEFORE the event loop, through the exact wire-Request path — a rolled\r\n    // ring can no longer replay it. Non-Request seeds are ignored (the opener\r\n    // of an attach stream is always its Request).\r\n    if let Some(AttachRecord::Request {\r\n        session_id,\r\n        from_seq,\r\n        intent: req_intent,\r\n        endpoint_id: _,\r\n    }) = seed_request\r\n    {\r\n        if let Some(out) = handle_attach_request(\r\n            brain,\r\n            &mut wire,\r\n            stream_id,\r\n            endpoint,\r\n            origin_node,\r\n            remote_session,\r\n            session_id,\r\n            from_seq,\r\n            req_intent,\r\n            &mut intent,\r\n            &mut attached_session,\r\n            &mut attached,\r\n        )? {\r\n            return Ok(out);\r\n        }\r\n    }\r\n\r\n    loop {\r\n        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\r\n        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\r\n        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\r\n        // snap-above armed at attach, REQ-HAZARD-VIEWER-RING-ROLL-SNAP). Before a\r\n        // role resolves no Output flows, so a plain read is correct then.\r\n        // [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\r\n        let event = match (role, attached_session) {\r\n            (Some(ServeRole::Controller), Some(sid)) => {\r\n                match read_controller_event_resuming(brain, sid, Some(origin_node)) {\r\n                    Ok(ev) => ev,\r\n                    Err(e)\r\n                        if e.get_ref()\r\n                            .is_some_and(|inner| inner.is::<ControllerIrrecoverablyBehind>()) =>\r\n                    {\r\n                        // Irrecoverable: the dropped frames rolled out of the ring.\r\n                        // SURFACE a marked truncation to the operator (never a\r\n                        // silent skip = B2 lie, never a spin), then end cleanly.\r\n                        // Graceful continuation is deferred\r\n                        // (REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND).\r\n                        let notice =\r\n                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\r\n                        let line = ndjson::encode_line(&AttachRecord::Output {\r\n                            seq: brain.controller_resume_floor(),\r\n                            data_b64: encode_bytes(notice),\r\n                        });\r\n                        let _ = wire.net_stream_send(stream_id, &line, None, true);\r\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\r\n                        return Err(e);\r\n                    }\r\n                    Err(e) => {\r\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\r\n                        return Err(e);\r\n                    }\r\n                }\r\n            }\r\n            _ => brain.read_event()?,\r\n        };\r\n        match event {\r\n            // ── Wire → session: requests, input, resize. ───────────────────\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    match rec {\r\n                        AttachRecord::Request {\r\n                            session_id,\r\n                            from_seq,\r\n                            intent: req_intent,\r\n                            // The dispatcher already resolved endpoint_id→session\r\n                            // (passed as remote_session) BEFORE this loop; ignore\r\n                            // the wire copy here (a mid-loop re-resolve would swallow\r\n                            // interleaved attach records — see remote_session docs).\r\n                            endpoint_id: _,\r\n                        } => {\r\n                            if let Some(out) = handle_attach_request(\r\n                                brain,\r\n                                &mut wire,\r\n                                stream_id,\r\n                                endpoint,\r\n                                origin_node,\r\n                                remote_session,\r\n                                session_id,\r\n                                from_seq,\r\n                                req_intent,\r\n                                &mut intent,\r\n                                &mut attached_session,\r\n                                &mut attached,\r","truncated":true,"truncatedBy":"lines","totalLines":414,"totalBytes":8647,"outputLines":165,"outputBytes":8647,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"///\r\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\r\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\r\n/// record — a reconstruction that waited for one would idle forever. The\r\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\r\n/// wire-arriving Request (same gate, same subscribe); a Request that then\r\n/// replays from the ring anyway re-runs the same-identity re-take, which is\r\n/// silent and idempotent (the successor-re-take parity the fix must keep).\r\n// [impl->REQ-INST-8]\r\n// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]\r\n// [impl->REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT]\r\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\r\n// [impl->REQ-STREAM-OPENER-DURABLE]\r\n#[allow(clippy::too_many_arguments)]\r\npub fn serve_attach(\r\n    brain: &mut Brain,\r\n    broker_name: &str,\r\n    stream_id: u64,\r\n    endpoint: &str,\r\n    origin_node: &str,\r\n    net_from_seq: u64,\r\n    remote_session: Option<u64>,\r\n    seed_request: Option<AttachRecord>,\r\n) -> io::Result<AttachServeOutcome> {\r\n    brain.net_stream_subscribe(stream_id, net_from_seq)?;\r\n    // TWO-CONN SPLIT (REQ-HAZARD-ENDPOINT-RUN-ATTACH-OUTPUT): the wire FORWARDS\r\n    // (session Output / Size / Exit / Displaced / detach-finish onto the operator\r\n    // stream) ride a SEPARATE broker connection from `brain`. `brain` RECEIVES\r\n    // session output (broker→serve) — including the controller's synchronous,\r\n    // inline, multi-frame ring replay (`become_controller`), which holds the\r\n    // broker's single-threaded per-conn handler for the whole replay. If the\r\n    // forwards rode `brain` too, that stuck handler could not read them →\r\n    // serve→broker backs up while broker→serve replay backs up → mutual IPC\r\n    // backpressure DEADLOCK, and the operator receives ZERO bytes (the bug: an\r\n    // attach to an ALREADY-PRODUCING endpoint-run harness, non-empty ring). The\r\n    // two directions on independent single-threaded handlers cannot block each\r\n    // other. `send_stream` is stream_id-keyed (conn-agnostic), so forwarding from\r\n    // a second conn is correct. Order is preserved: this one serve thread reads\r\n    // every Output in seq order on `brain` and forwards it in that same order on\r\n    // `wire`. (DEEPER ROOT, post-v0.12.1 follow-up: the CONTROLLER replay is a\r\n    // SYNCHRONOUS inline write, while VIEWERS use the async dedicated-writer-\r\n    // thread + bounded-channel pattern (`add_viewer`/`viewer_writer`); unifying\r\n    // the controller onto that async path would remove the inline-replay block at\r\n    // the source, but it touches the authoritative cursor-advancing path.)\r\n    let mut wire = Brain::cold_start(broker_name, now_ms())?;\r\n    let mut decoder = AttachDecoder::new();\r\n    let mut attached = false;\r\n    // The Request's intent — gates wire input (a viewer's keystrokes never reach\r\n    // the PTY) independent of the broker's later role reply (no IPC-ordering\r\n    // race on the input gate).\r\n    let mut intent = AttachIntent::Control;\r\n    // The broker-confirmed role (arrives as `Subscribed`). `None` until then:\r\n    // controller input that races ahead of the reply is BUFFERED, not dropped\r\n    // (the replayed-input-after-restart case) and not leaked to the PTY before\r\n    // we know we control the session (the busy-refuse case).\r\n    let mut role: Option<ServeRole> = None;\r\n    let mut pending_input: Vec<(u64, Vec<u8>)> = Vec::new();\r\n    // The attached session — captured from the Request so the terminal paths can\r\n    // explicitly release this worker's role (clearing driven_by / viewer_count\r\n    // promptly, not only on connection drop).\r\n    let mut attached_session: Option<u64> = None;\r\n    // Last skip-to-live re-subscribe (REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT): bounds the\r\n    // evict→resubscribe rate so a hopelessly-behind viewer under flood sees\r\n    // intermittent live bursts, never a CPU spin.\r\n    let mut last_resubscribe: Option<Instant> = None;\r\n\r\n    // Durable-opener seed (ADR-0038 B): process the dispatcher-pinned Request\r\n    // BEFORE the event loop, through the exact wire-Request path — a rolled\r\n    // ring can no longer replay it. Non-Request seeds are ignored (the opener\r\n    // of an attach stream is always its Request).\r\n    if let Some(AttachRecord::Request {\r\n        session_id,\r\n        from_seq,\r\n        intent: req_intent,\r\n        endpoint_id: _,\r\n    }) = seed_request\r\n    {\r\n        if let Some(out) = handle_attach_request(\r\n            brain,\r\n            &mut wire,\r\n            stream_id,\r\n            endpoint,\r\n            origin_node,\r\n            remote_session,\r\n            session_id,\r\n            from_seq,\r\n            req_intent,\r\n            &mut intent,\r\n            &mut attached_session,\r\n            &mut attached,\r\n        )? {\r\n            return Ok(out);\r\n        }\r\n    }\r\n\r\n    loop {\r\n        // The CONTROLLER resumes-from-floor on a b4 drop-don't-block output-gap\r\n        // (RE-FETCH from delivered_through, NOT snap — B2: a controller is\r\n        // authoritative). A VIEWER never reaches the reject-gap (its serve-brain is\r\n        // snap-above armed at attach, REQ-HAZARD-VIEWER-RING-ROLL-SNAP). Before a\r\n        // role resolves no Output flows, so a plain read is correct then.\r\n        // [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\r\n        let event = match (role, attached_session) {\r\n            (Some(ServeRole::Controller), Some(sid)) => {\r\n                match read_controller_event_resuming(brain, sid, Some(origin_node)) {\r\n                    Ok(ev) => ev,\r\n                    Err(e)\r\n                        if e.get_ref()\r\n                            .is_some_and(|inner| inner.is::<ControllerIrrecoverablyBehind>()) =>\r\n                    {\r\n                        // Irrecoverable: the dropped frames rolled out of the ring.\r\n                        // SURFACE a marked truncation to the operator (never a\r\n                        // silent skip = B2 lie, never a spin), then end cleanly.\r\n                        // Graceful continuation is deferred\r\n                        // (REQ-HAZARD-CONTROLLER-IRRECOVERABLE-BEHIND).\r\n                        let notice =\r\n                            b\"\\r\\n[spt: output truncated -- controller fell behind the live ring, frames lost]\\r\\n\";\r\n                        let line = ndjson::encode_line(&AttachRecord::Output {\r\n                            seq: brain.controller_resume_floor(),\r\n                            data_b64: encode_bytes(notice),\r\n                        });\r\n                        let _ = wire.net_stream_send(stream_id, &line, None, true);\r\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\r\n                        return Err(e);\r\n                    }\r\n                    Err(e) => {\r\n                        feed_rest(endpoint, crate::resting::RestEvent::Detach);\r\n                        return Err(e);\r\n                    }\r\n                }\r\n            }\r\n            _ => brain.read_event()?,\r\n        };\r\n        match event {\r\n            // ── Wire → session: requests, input, resize. ───────────────────\r\n            BrokerEvent::NetStreamData {\r\n                stream_id: sid,\r\n                bytes,\r\n                ..\r\n            } if sid == stream_id => {\r\n                for rec in decoder.push(&bytes) {\r\n                    match rec {\r\n                        AttachRecord::Request {\r\n                            session_id,\r\n                            from_seq,\r\n                            intent: req_intent,\r\n                            // The dispatcher already resolved endpoint_id→session\r\n                            // (passed as remote_session) BEFORE this loop; ignore\r\n                            // the wire copy here (a mid-loop re-resolve would swallow\r\n                            // interleaved attach records — see remote_session docs).\r\n                            endpoint_id: _,\r\n                        } => {\r\n                            if let Some(out) = handle_attach_request(\r\n                                brain,\r\n                                &mut wire,\r\n                                stream_id,\r\n                                endpoint,\r\n                                origin_node,\r\n                                remote_session,\r\n                                session_id,\r\n                                from_seq,\r\n                                req_intent,\r\n                                &mut intent,\r\n                                &mut attached_session,\r\n                                &mut attached,\r\n                            )? {\n…\n                            }\n                        }\n…\n                    }\n                }\n            }\n…\n        }\n    }\n}","startLine":279,"lineNumbers":[279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,null,446,447,null,489,490,491,null,605,606,607]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\attach.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":692,"totalBytes":8647,"outputLines":165,"outputBytes":8647,"shownRange":{"start":279,"end":443},"nextOffset":444}}},"isError":false,"timestamp":1784247617518}}
{"type":"message","id":"067938ba","parentId":"a14c9f0f","timestamp":"2026-07-17T00:20:26.790Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Tracing broker connection handling**\n\n**Analyzing incoming stream deadlock cause**"},{"type":"toolCall","id":"call_Be8UHCCgQfOPdp1eZQOabkgt|fc_0ceecce325b7fe2d016a59754a59dc819892fd5440e4a41e18","name":"grep","arguments":{"i":"Inspecting seeded attach handshake","pattern":"fn handle_attach_request|read_event\\(|Subscribed","path":"crates/spt-daemon/src/attach.rs","selector":"80-280","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5225,"output":291,"cacheRead":71168,"cacheWrite":0,"totalTokens":76684,"reasoningTokens":222,"cost":{"input":0.026125000000000002,"output":0.00873,"cacheRead":0.035584,"cacheWrite":0,"total":0.070439}},"stopReason":"toolUse","timestamp":1784247617532,"responseId":"resp_0ceecce325b7fe2d016a597541bf448198bd2036dea86f235d","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a597543d10081988cc6fb25a06c7270","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXVKbl04eD2p8SAxO7ELinlFOPbZ1IRABjIE5srZXMr9a0jADZu6QYN9VYuwBRLrJhaaLpkU2J1bN8R9BOjj8cAwTkJW2SiXQuOcG9JL8sWyqND1oUMZpdfqquMMFhX1aFxRx7Ji38jvu6tXrfQb4On8oVNjxomjp4LVcflNNK1GsejPECjmshIwvF3QTaLPbJlnLzvxjfERE2EiEajoiuVgU27pB5knm76dWjZdOqyUVpnGniB7knsShYGhu4nyvOYlY4uyCcqZW0gGCqB3ODfKuku5VgUiOjkUP98GEf-qlxeeZzpCcNM8RqfzKHC03IJDxn3WaYbHvNykJqpxMuMjkpjTom9rdyMqQ3sYhqw0rXn3xzgnfBKznHbIh8HHK5dSVkW0u65DkYRMf89pfiWqPpG7i_jsTZvjB78o1wGB7nJ0kspAaxfGkXYUQlo-yWQJsrRu08LSqVac3Kve1l4D8VTRQTgHpmZbSEbwEexEbYCoZFsgcb4o-v4H5WaKQxdA0q2R4oDfoN54o1lBqozmIDuugHGvDBBoxuZUWTNCKLL14PX37SNYwLiRT0G-gahO3M1_Yicyk6bjMxx7qI8_P3KdN927xCytHwaCl7W-2h-WjH2VyN1VpGw-CrFJdSm5zWVDcgr-8rGBGsSchgXpoNxk-tS0yrjm0iztuyne_iFTznO_U3pzxaiZuTFJAH8fUREhXlqoSUSGBo8zP641Txw8dEz0R4j2s0jHtwPxEQnlb2vFyfOUPoba4z1ymqNnREFwWfAPfALx40UaiLAR206URuDARgwZBN0PP0_GUkeQcSMcgvaiXWvVjo_JrB7nzzArfit2wnbHYbUBFIaUVWQSJhEVE2uVIpKKA3xorjGqzA2qieppu3QKMf6gL0UE2MpFrghyC-1yGxXs5s5hfaR8YGpl0XnfD-2YmFjllOYAMsv8-jSQRRJJHZxrAKF9YBKB8KML7QsN_LXxyw6aZT20wgP885XG4xnY5u20h3Bm8tN-CXuZOnSW2UvHZ8OBkRnjkwbmG6YcxWMANANPfVlhr3ZvAROjf1YVmv6kzd0lwkgejlmX3ej3wpBIfH7xJJwsDL28nTg6mf0U6Gla2j_A7qK0pmhZh_KwKi3ojWr_Rwk0ySDF8qsSMNovfYPAV4GfbAC-ZP3XNVX19_36SZkBW_YPv3qO0oqDJr8ZMmBL_wVi7bYJrqWE-l3EsIHvB47xcEbwS0dJRTvkFKf3iJUxWccHcBlvvlKbtzIxH-c97yTg5NyatOz258pFXocONdf6TH4jDIH6bJQjYnwv48uq4CdKwqEcFmDZRtPqIOXIuCZTAWb7QkLs8gKYTzeLvRNP151w7q3CoaClHvlqTXHq9WGk0o5Tqtuqz2mbXS6PBzBUZGjOYuBYBztOe9eSbJKrOA0uwinZ7rHcSIlsO2FOA0s7ILdQyW3LIFMl66eL8ymgnztEx47TTtfeyFW7cIADF7L108bjp5ZFb_hqSVJQWU4kwuPLNm_YySFbW-a-cyW9tQLyMhCcFdqjMmGtPIm0uaWdnV__Zj3fSkebuYTZXhVxtqt7zlkZ7CTm_t_cuqfqS-I2eqQ-mEjbVeZzln06H1f1dmuAHavMX0B_PIohaS5MBe4xU1GpdLNaRVw-8ElfKb9WysLh8LG8BOOQyLUIlRCqq1Zzg3sP-c82flWsQN6f8kHsjjgNzx1muRnwH1kRhlDwfpP9GLWYwGTEp4chdfXh3MJGLLDIY-VcnKHUyhmIUF-Rk6rl12OhR0LhlarnATN8-gxvrnrjPcKHgZ0gu0ZjJHPClcnjX4TLgTokHPs-8dk36s1haqwGG1-g9Au3sfqULTCvT6wGWHOtfu9DPLlSXid4dxl8MUo13bdJ1-GaNOB0JRYdsfD0ggs8rgVG6cTD61okwnzd4nsi5Har5evjiWrsb8zoV5wrCgNLAXI7WBhmxH34N4CRm72yOsninh6rDDfAwIaHsUb9xMib9ttlzUUSG-DMqVBBvCdy6wmQXRuIjsx4Y0SvKJrHlSngLJkc2za-c8C_yNwXqZzHcgzHRTXp3oSjEXYqli5gUErW-2V7mYSYhw9BvccJx164ahNnH2O5_B-m8Bu7T8HgUo27N81Czz6V1F8zrqYpo1w3b0LFb59wsHy2p_IEz8318UsRjKBTBnycWC7o4eSA3XxrqgyYamdWYeHb_40QrVCwKQcBIH_6sEMydDLs5zSTXRjiATN7QvGOkGv-aVpTMRpiLb3iaZdTzNx2WMPB0Q1LpbWNZwWHGZlUd6vr0PPrmdglQeP0iU9Pje-DuvxQWwCNxM9licv6XN_vBFEScC51MbsB5LqPEBfimYPzTUzSyNE4L9A-ZuXENKTVnO-JEXH2c3lTy5XalLtm7cepk3yj32VD2akgg97btOneDsi4dosR6IQVMDsHQBXY","summary":[{"type":"summary_text","text":"**Tracing broker connection handling**"},{"type":"summary_text","text":"**Analyzing incoming stream deadlock cause**"}]},{"id":"fc_0ceecce325b7fe2d016a59754a59dc819892fd5440e4a41e18","type":"function_call","status":"completed","arguments":"{\"i\":\"Inspecting seeded attach handshake\",\"pattern\":\"fn handle_attach_request|read_event\\\\(|Subscribed\",\"path\":\"crates/spt-daemon/src/attach.rs\",\"selector\":\"80-280\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_Be8UHCCgQfOPdp1eZQOabkgt","name":"grep"}]},"duration":9248.963200002909,"ttft":2416.216099999845,"contextSnapshot":{"promptTokens":76393,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Be8UHCCgQfOPdp1eZQOabkgt|fc_0ceecce325b7fe2d016a59754a59dc819892fd5440e4a41e18","toolName":"grep","startedAt":"2026-07-17T00:20:26.790Z","args":{"path":"crates/spt-daemon/src/attach.rs"},"intent":"Inspecting seeded attach handshake"},"id":"ba4be1c3","parentId":"067938ba","timestamp":"2026-07-17T00:20:26.791Z"}
{"type":"message","id":"5cfdae9c","parentId":"ba4be1c3","timestamp":"2026-07-17T00:20:26.798Z","message":{"role":"toolResult","toolCallId":"call_Be8UHCCgQfOPdp1eZQOabkgt|fc_0ceecce325b7fe2d016a59754a59dc819892fd5440e4a41e18","toolName":"grep","content":[{"type":"text","text":" 153|/// caller surfaces it (marked truncation), never silent-skips (B2 lie) and never\n*154|/// spins. The helper swallows the `Subscribed` ack its own re-subscribe produces so\n 155|/// the repeated-gap detection is not masked. Non-`Output` events (NetStreamData /\n 156|/// Size / Exit / …) pass through unchanged.\n 157|// [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n...\n 165|    loop {\n*166|        match brain.read_event() {\n*167|            // Our own re-subscribe's Subscribed reply: swallow it (role is already\n 168|            // resolved) so it cannot mask a repeated gap as forward progress.\n*169|            Ok(BrokerEvent::Subscribed { .. }) if swallow_resume_ack => {\n 170|                swallow_resume_ack = false;\n 171|                continue;\n 172|            }\n...\n 222|/// - Role-aware subscribe (REQ-RCVIEW-1): the broker resolves controller /\n*223|///   viewer / busy / take by intent + origin and replies a `Subscribed`;\n 224|///   driven_by / viewer_count are BROKER-owned (single writer). A VIEWER\n 225|///   attach ARMS snap-above at the floor (REQ-HAZARD-VIEWER-RING-ROLL-SNAP);\n 226|///   the CONTROLLER keeps strict reject-gap (exactly-once resume, B2).\n...\n 229|#[allow(clippy::too_many_arguments)]\n*230|fn handle_attach_request(\n 231|    brain: &mut Brain,\n 232|    wire: &mut Brain,\n 233|    stream_id: u64,\n...\n 276|/// round-trip inside the event loop would swallow interleaved attach records\n*277|/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\n 278|/// the wire `session_id`.\n 279|///\n 280|/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the"}],"details":{"scopePath":"crates/spt-daemon/src/attach.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/attach.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":7,"fileCount":1,"files":["crates/spt-daemon/src/attach.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/attach.rs","count":7}],"truncated":false,"displayContent":" 153│/// caller surfaces it (marked truncation), never silent-skips (B2 lie) and never\n*154│/// spins. The helper swallows the `Subscribed` ack its own re-subscribe produces so\n 155│/// the repeated-gap detection is not masked. Non-`Output` events (NetStreamData /\n 156│/// Size / Exit / …) pass through unchanged.\n 157│// [impl->REQ-HAZARD-CONTROLLER-GAP-RESUME]\n    │...\n 165│    loop {\n*166│        match brain.read_event() {\n*167│            // Our own re-subscribe's Subscribed reply: swallow it (role is already\n 168│            // resolved) so it cannot mask a repeated gap as forward progress.\n*169│            Ok(BrokerEvent::Subscribed { .. }) if swallow_resume_ack => {\n 170│                swallow_resume_ack = false;\n 171│                continue;\n 172│            }\n    │...\n 222│/// - Role-aware subscribe (REQ-RCVIEW-1): the broker resolves controller /\n*223│///   viewer / busy / take by intent + origin and replies a `Subscribed`;\n 224│///   driven_by / viewer_count are BROKER-owned (single writer). A VIEWER\n 225│///   attach ARMS snap-above at the floor (REQ-HAZARD-VIEWER-RING-ROLL-SNAP);\n 226│///   the CONTROLLER keeps strict reject-gap (exactly-once resume, B2).\n    │...\n 229│#[allow(clippy::too_many_arguments)]\n*230│fn handle_attach_request(\n 231│    brain: &mut Brain,\n 232│    wire: &mut Brain,\n 233│    stream_id: u64,\n    │...\n 276│/// round-trip inside the event loop would swallow interleaved attach records\n*277│/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\n 278│/// the wire `session_id`.\n 279│///\n 280│/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the"},"isError":false,"timestamp":1784247626797}}
{"type":"message","id":"849e1027","parentId":"5cfdae9c","timestamp":"2026-07-17T00:20:30.219Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_pwp0ZeT7I9P34ZseOCvgQ4hg|fc_0ceecce325b7fe2d016a59754d26bc819888dafaeba1b21633","name":"read","arguments":{"i":"Reading attach request handler","path":"crates/spt-daemon/src/attach.rs","selector":"220-280"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3932,"output":39,"cacheRead":73216,"cacheWrite":0,"totalTokens":77187,"cost":{"input":0.01966,"output":0.00117,"cacheRead":0.036608,"cacheWrite":0,"total":0.057438}},"stopReason":"toolUse","timestamp":1784247626818,"responseId":"resp_0ceecce325b7fe2d016a59754afce4819898f64d052e516cad","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_0ceecce325b7fe2d016a59754d26bc819888dafaeba1b21633","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading attach request handler\",\"path\":\"crates/spt-daemon/src/attach.rs\",\"selector\":\"220-280\"}","call_id":"call_pwp0ZeT7I9P34ZseOCvgQ4hg","name":"read"}]},"duration":3400.89090000093,"ttft":2343.7518000006676,"contextSnapshot":{"promptTokens":77148,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_pwp0ZeT7I9P34ZseOCvgQ4hg|fc_0ceecce325b7fe2d016a59754d26bc819888dafaeba1b21633","toolName":"read","startedAt":"2026-07-17T00:20:30.219Z","args":{"path":"crates/spt-daemon/src/attach.rs"},"intent":"Reading attach request handler"},"id":"954d41e0","parentId":"849e1027","timestamp":"2026-07-17T00:20:30.219Z"}
{"type":"message","id":"11639173","parentId":"954d41e0","timestamp":"2026-07-17T00:20:30.237Z","message":{"role":"toolResult","toolCallId":"call_pwp0ZeT7I9P34ZseOCvgQ4hg|fc_0ceecce325b7fe2d016a59754d26bc819888dafaeba1b21633","toolName":"read","content":[{"type":"text","text":"/// - Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): the dispatcher-resolved\n///   session id wins (the owning node is authoritative for its own table);\n///   the LOCAL path uses the wire `session_id` unchanged.\n/// - Role-aware subscribe (REQ-RCVIEW-1): the broker resolves controller /\n///   viewer / busy / take by intent + origin and replies a `Subscribed`;\n///   driven_by / viewer_count are BROKER-owned (single writer). A VIEWER\n///   attach ARMS snap-above at the floor (REQ-HAZARD-VIEWER-RING-ROLL-SNAP);\n///   the CONTROLLER keeps strict reject-gap (exactly-once resume, B2).\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\n// [impl->REQ-HAZARD-VIEWER-RING-ROLL-SNAP]\n#[allow(clippy::too_many_arguments)]\nfn handle_attach_request(\n    brain: &mut Brain,\n    wire: &mut Brain,\n    stream_id: u64,\n    endpoint: &str,\n    origin_node: &str,\n    remote_session: Option<u64>,\n    session_id: u64,\n    from_seq: u64,\n    req_intent: AttachIntent,\n    intent: &mut AttachIntent,\n    attached_session: &mut Option<u64>,\n    attached: &mut bool,\n) -> io::Result<Option<AttachServeOutcome>> {\n    if access_check(endpoint, origin_node, InboundClass::Unsolicited) == AccessDecision::Deny {\n        wire.net_stream_send(stream_id, &[], None, true)?;\n        return Ok(Some(AttachServeOutcome::Refused));\n    }\n    let sid = remote_session.unwrap_or(session_id);\n    *intent = req_intent;\n    *attached_session = Some(sid);\n    if req_intent == AttachIntent::Viewer {\n        brain.attach_as_viewer_snap(sid, from_seq, Some(origin_node))?;\n    } else {\n        brain.attach_as(sid, from_seq, req_intent, Some(origin_node))?;\n    }\n    *attached = true;\n    Ok(None)\n}\n\n/// Serve one attach stream against this brain's broker, blocking until the\n/// session exits, the operator detaches, or the gate refuses.\n///\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\n/// is being attached, for the gate. `net_from_seq` is this brain's resume\n/// cursor on the attach stream (0 cold — also the safe post-restart choice:\n/// the replayed `Request`/`Input` records re-gate and dedup at the PTY\n/// journal, and re-pumped output dedups at the operator's render cursor).\n///\n/// `remote_session` is `Some(sid)` ONLY on the cross-node leg (#4,\n/// REQ-RC-CROSS-NODE-ATTACH): the dispatcher has ALREADY resolved endpoint→our\n/// session id ([`resolve_local_session`]) BEFORE this serve loop subscribed, so\n/// the wire's placeholder `session_id` (0) is overridden here with the resolved\n/// id. It is passed down — never re-queried mid-loop — because a `sessions()`\n/// round-trip inside the event loop would swallow interleaved attach records\n/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\n/// the wire `session_id`.\n///\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\n/// record — a reconstruction that waited for one would idle forever. The\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\n\n[Showing lines 219-283 of 692. Use :284 to continue]"}],"details":{"truncation":{"content":"/// - Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): the dispatcher-resolved\r\n///   session id wins (the owning node is authoritative for its own table);\r\n///   the LOCAL path uses the wire `session_id` unchanged.\r\n/// - Role-aware subscribe (REQ-RCVIEW-1): the broker resolves controller /\r\n///   viewer / busy / take by intent + origin and replies a `Subscribed`;\r\n///   driven_by / viewer_count are BROKER-owned (single writer). A VIEWER\r\n///   attach ARMS snap-above at the floor (REQ-HAZARD-VIEWER-RING-ROLL-SNAP);\r\n///   the CONTROLLER keeps strict reject-gap (exactly-once resume, B2).\r\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\r\n// [impl->REQ-HAZARD-VIEWER-RING-ROLL-SNAP]\r\n#[allow(clippy::too_many_arguments)]\r\nfn handle_attach_request(\r\n    brain: &mut Brain,\r\n    wire: &mut Brain,\r\n    stream_id: u64,\r\n    endpoint: &str,\r\n    origin_node: &str,\r\n    remote_session: Option<u64>,\r\n    session_id: u64,\r\n    from_seq: u64,\r\n    req_intent: AttachIntent,\r\n    intent: &mut AttachIntent,\r\n    attached_session: &mut Option<u64>,\r\n    attached: &mut bool,\r\n) -> io::Result<Option<AttachServeOutcome>> {\r\n    if access_check(endpoint, origin_node, InboundClass::Unsolicited) == AccessDecision::Deny {\r\n        wire.net_stream_send(stream_id, &[], None, true)?;\r\n        return Ok(Some(AttachServeOutcome::Refused));\r\n    }\r\n    let sid = remote_session.unwrap_or(session_id);\r\n    *intent = req_intent;\r\n    *attached_session = Some(sid);\r\n    if req_intent == AttachIntent::Viewer {\r\n        brain.attach_as_viewer_snap(sid, from_seq, Some(origin_node))?;\r\n    } else {\r\n        brain.attach_as(sid, from_seq, req_intent, Some(origin_node))?;\r\n    }\r\n    *attached = true;\r\n    Ok(None)\r\n}\r\n\r\n/// Serve one attach stream against this brain's broker, blocking until the\r\n/// session exits, the operator detaches, or the gate refuses.\r\n///\r\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\r\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\r\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\r\n/// is being attached, for the gate. `net_from_seq` is this brain's resume\r\n/// cursor on the attach stream (0 cold — also the safe post-restart choice:\r\n/// the replayed `Request`/`Input` records re-gate and dedup at the PTY\r\n/// journal, and re-pumped output dedups at the operator's render cursor).\r\n///\r\n/// `remote_session` is `Some(sid)` ONLY on the cross-node leg (#4,\r\n/// REQ-RC-CROSS-NODE-ATTACH): the dispatcher has ALREADY resolved endpoint→our\r\n/// session id ([`resolve_local_session`]) BEFORE this serve loop subscribed, so\r\n/// the wire's placeholder `session_id` (0) is overridden here with the resolved\r\n/// id. It is passed down — never re-queried mid-loop — because a `sessions()`\r\n/// round-trip inside the event loop would swallow interleaved attach records\r\n/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\r\n/// the wire `session_id`.\r\n///\r\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\r\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\r\n/// record — a reconstruction that waited for one would idle forever. The\r\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\r","truncated":true,"truncatedBy":"lines","totalLines":474,"totalBytes":3292,"outputLines":65,"outputBytes":3292,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"/// - Cross-node leg (#4, REQ-RC-CROSS-NODE-ATTACH): the dispatcher-resolved\r\n///   session id wins (the owning node is authoritative for its own table);\r\n///   the LOCAL path uses the wire `session_id` unchanged.\r\n/// - Role-aware subscribe (REQ-RCVIEW-1): the broker resolves controller /\r\n///   viewer / busy / take by intent + origin and replies a `Subscribed`;\r\n///   driven_by / viewer_count are BROKER-owned (single writer). A VIEWER\r\n///   attach ARMS snap-above at the floor (REQ-HAZARD-VIEWER-RING-ROLL-SNAP);\r\n///   the CONTROLLER keeps strict reject-gap (exactly-once resume, B2).\r\n// [impl->REQ-RC-CROSS-NODE-ATTACH]\r\n// [impl->REQ-HAZARD-VIEWER-RING-ROLL-SNAP]\r\n#[allow(clippy::too_many_arguments)]\r\nfn handle_attach_request(\r\n    brain: &mut Brain,\r\n    wire: &mut Brain,\r\n    stream_id: u64,\r\n    endpoint: &str,\r\n    origin_node: &str,\r\n    remote_session: Option<u64>,\r\n    session_id: u64,\r\n    from_seq: u64,\r\n    req_intent: AttachIntent,\r\n    intent: &mut AttachIntent,\r\n    attached_session: &mut Option<u64>,\r\n    attached: &mut bool,\r\n) -> io::Result<Option<AttachServeOutcome>> {\r\n    if access_check(endpoint, origin_node, InboundClass::Unsolicited) == AccessDecision::Deny {\r\n        wire.net_stream_send(stream_id, &[], None, true)?;\r\n        return Ok(Some(AttachServeOutcome::Refused));\r\n    }\r\n    let sid = remote_session.unwrap_or(session_id);\r\n    *intent = req_intent;\r\n    *attached_session = Some(sid);\r\n    if req_intent == AttachIntent::Viewer {\r\n        brain.attach_as_viewer_snap(sid, from_seq, Some(origin_node))?;\r\n    } else {\r\n        brain.attach_as(sid, from_seq, req_intent, Some(origin_node))?;\r\n    }\r\n    *attached = true;\r\n    Ok(None)\r\n}\r\n\r\n/// Serve one attach stream against this brain's broker, blocking until the\r\n/// session exits, the operator detaches, or the gate refuses.\r\n///\r\n/// `origin_node` MUST be the handshake-proven remote id from the broker's\r\n/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes\r\n/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session\r\n/// is being attached, for the gate. `net_from_seq` is this brain's resume\r\n/// cursor on the attach stream (0 cold — also the safe post-restart choice:\r\n/// the replayed `Request`/`Input` records re-gate and dedup at the PTY\r\n/// journal, and re-pumped output dedups at the operator's render cursor).\r\n///\r\n/// `remote_session` is `Some(sid)` ONLY on the cross-node leg (#4,\r\n/// REQ-RC-CROSS-NODE-ATTACH): the dispatcher has ALREADY resolved endpoint→our\r\n/// session id ([`resolve_local_session`]) BEFORE this serve loop subscribed, so\r\n/// the wire's placeholder `session_id` (0) is overridden here with the resolved\r\n/// id. It is passed down — never re-queried mid-loop — because a `sessions()`\r\n/// round-trip inside the event loop would swallow interleaved attach records\r\n/// (`Input`/`Subscribed`) the loop must see. `None` = the local path, which uses\r\n/// the wire `session_id`.\r\n///\r\n/// `seed_request` is the broker-pinned opener Request (ADR-0038 B): after the\r\n/// bounded ring rolls, the wire replay no longer carries the stream's Request\r\n/// record — a reconstruction that waited for one would idle forever. The\r\n/// dispatcher hands the durable opener here and it is handled EXACTLY like a\r","startLine":219,"lineNumbers":[219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\attach.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":692,"totalBytes":3292,"outputLines":65,"outputBytes":3292,"shownRange":{"start":219,"end":283},"nextOffset":284}}},"isError":false,"timestamp":1784247630237}}
{"type":"message","id":"5f48a681","parentId":"11639173","timestamp":"2026-07-17T00:21:09.079Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing client-server buffering deadlock**\n\n**Assessing thread overload and cancellation delays**\n\n**Investigating delayed WriteFileEx completions**"},{"type":"thinking","thinking":"**Analyzing write poisoning timing and locking**\n\n**Tracing version regression in connection retries**\n\n**Classifying stream roles by IDs**"},{"type":"toolCall","id":"call_05GlrX2Je8d41i72mspWnIeJ|fc_0ceecce325b7fe2d016a59756a5f048198b025205f1731ac42","name":"irc","arguments":{"i":"Reporting field causal mechanism","op":"send","to":"Main","message":"Causal tie found. v0.34 W1 makes Failed claims retry at +500ms/+1s (max3); fresh dispatch has no concurrency bound and thread::spawn's every claimed row in one 100ms sweep (dispatch.rs 299-361). Each Attach worker net-stream-subscribes then immediately sends session attach before reading; broker nethost StreamLog::attach synchronously replays ring while holding that stream log mutex (285-351,1676-93). With 4,361 workers/attaches, client worker can be descheduled/not drain; broker WriteFileEx blocks and poisons at 15s. Poison log is NOT completion: conn.rs watchdog logs/cancels at deadline, then re-cancels every 100ms while inflight remains and severs only after write_frame returns (345-399, 540-593). Ordinary Brain::Whole read has no timeout (brain.rs 1734-49), so worker/DISPATCH failure appears only after sever/EOF; variable +0.1..47s after poison matches delayed cancellation completion + 4k-thread scheduling. Direct effect is attach stream/input forwarding stall, not ConPTY child output; per-stream mutex means not broker-wide unless fan-out overload. v34 didn't add 15s timer (pre-v34), but retry converts one abandon into up to 3 15s replays and pinned-opener/fallback adds IPC. Finished Attach should retire before subscribe; correlate poison stream IDs/opener family/finished flag. If 4834 is active controller Attach, strong direct cause. Fix: bound dispatcher concurrency + make StreamLog.attach replay async/off-lock/nonblocking; stop retrying deadline-poison blindly; sever client read at deadline without waiting write completion if safe. Test real 5+ active PTYs + thousands stale rows, production 15s/cancel lag, assert continuous input/output and bounded worker/conns.","replyTo":null,"await":false,"from":null,"timeoutMs":null,"peek":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3802,"output":1467,"cacheRead":74240,"cacheWrite":0,"totalTokens":79509,"reasoningTokens":1020,"cost":{"input":0.019010000000000003,"output":0.04401,"cacheRead":0.03712,"cacheWrite":0,"total":0.10014}},"stopReason":"toolUse","timestamp":1784247630250,"responseId":"resp_0ceecce325b7fe2d016a597550ea9881989f7617ffa843fcaa","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a597552a2c0819892ac8867c8bac4f9","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXVfrBN-I88KYWt-_XA3JQBovi15y1P7cOgsu5d2qmcgY59YRlzNwufOhBPTYIbPsgwcadq2au1sD3ULf7ytLLlvQiWh5oBJcO8gIHrktG74TNxGcRqRF2tJjurEu-lOixKranWMX5dhBxUJB5nXX94iLDDVce5gPPWk2VSe5lyQ4depWy3wDGXdmXA06SNYExK-26J8Ry7CEWfHf8zfHwl3v42yp1Sj_Zv4OUFYnT57L6TT7MDIPJyoVGz4IyPb-LyzxhkhSyXIyw02rm5JOQBP49ttQ0ecGSkgJHGFuqt-POg9jkxLQGNwXQS788G1UNStpSH77MkFqcm6WOCTcLmc7pP6h4oq04CmGGxl12F723gQNfQ8OaLUB25Qo3F7s7inQvmRhDugglrbmu15ENxPh5QnPJkznfD9Q1kBglux5Jo3t2o4MGTIx0XeOLYGcF6rIQRkFqiPAY-cWIp69mZwn_o2rD6r0mzjAntWGCqqbfE6gNz8gA6q6ChfboaqBGGJwE2TNJ3A1G1mNaJ2oaUx1ADKC9dSH-TatpJ_eD_0a2jgbvZC1ZsJFZWFv36Tqv_5LT30fv1VGwjPo4DgDvmjZWqQ2ISpgQjEA1GQfQ5M49kYmFC3LOJ7180tvlYqX5VHnibP4nO63vYZGSY06KrqcZUtI3eP6dMLVcCNU_mBXpX1gqbg1ZEE-mfBPaiNyos4iIwbPYi05UMDBMAdlhyF1EoEgFcyT8DJag9wTYyPZ8auG6XFLEY9s2BNlylSA--6T_Oa89gd8llr08aDoz-ooKwX1PaMjMi6MsDPNvoaOEYMCqwTJmXMcbkTLiq4Qu8gNKEls8OpN4_w38rYyn5WPQ_D30nYHf4wnupB2BNKhTS7HdtK-Jw95pW4Hn8cRlEvAhbR_5omqxoHpQ_79ED9bDIF8kuc_ZDmIg_T1_dzv5UPcWeMkNBM1o4XXzRMo9RV9J68YVEAKLPSipej5tOyBvPD6GxofziWWtEz_fEpG3gh28_218_RzdaAqNz8wAY6ak8Ih8O8mkjENtpoF0pDR93SAZcJoVshBZAJihOvHzXeqrYVyxocubFDOouRNrnR6LFBeHuUahbeKsIrKn-QNIgJHnHIGIp2TpXGccX0dSm54UIfhBlY1MZ1nCU2lSdu-A5O-kzXywb6Dl1CiZcKehDF7tZC5YptPaZ4lVRCUhV0w1u507XodOIQ81UJxkIMtZDCcy83hY-YmsZWGjSunlZGatm8q5TONJjfoKfIM8GTDESzp_UrmVIOSyh3Smba_Nhl2Em_kA1-nvX0lbwm7d8jpF8dO-YrVOsoRK6gaBtOYOo32jPXosI86iOHcRnOolnHnwXeThwniPUpLiqVF-xHWJtZ8nJp_TnZDq_kbSI6mUHIroVQc03SK2Fp7WQA3rQ3reBIibortGuJ5Wis4KePaZa9aAN416rt7oKeKvORDgiihWNC8Fd0Ejb6jkZ2qVUxS-6T0qa-EKunt5mpFtj-WGGqtvfpewUoWRpT1JCSSkw7EsUgTerajpf1Hm4S1hXAQkc0VwSRC4xat3FVdNI1qsW_FNVSXy_CCCsKZvVw3tuSlT69CEKz5WkXiXODWdRqZvCJTAdxQN7aXbKLt9Asy1oBac7YyELNGQ0JMEmxWhnd8VscGsB6nUffMgf6wlMhtwte1V3V9p0K-uS_-pLVySNMH8VqbER00bpaIDRDijU5o_Yuy_YFLux8qLc5VdpyRBvrrgS8KAgZ00mxft5skUlz4-TphPuY9Ytu1F4ikpzhC_2ARASjAcmhxPKWtJBVR3ZJ55PbPPW3SBcR99JPJGSZiBe7x442ronjdHLBG2WUJYJiLKvUJz_MO8nrDTw9wiwGuKesDbqfNVyVyKbYBPHmmBxAz9PMx6qph8K5N1ZGIbHNJPUeT5Y7pYUZj1ngk6wYx5WP8iLe3w7Jdv0ouDD3xYh0cw27FGCcK4z7wrbzNYIqVGiSH3DYE8lGxLASu_dDaklS4ADbfEPIlewrWc-6pyDzfc8UtpOPjMm_p5pJGc1PgLnyt6a5qs9cEhePhn5dWsExCm75N4adt1e33ryw8gVHKVTDmK7wzmMh1NP6f78VyIfXh1gDJ6QfhFn9B-yU_JOtlrnnhLY0MAV_mAJ84wchXTnWYLEPmvAhIW5iYTPLqYlZpDeGouvwKbIDQDGfAFtU9RmEl9e6-hCZq1j-ZceZLBv9F2yI-TFM7SpHNYsuLEprgdQGxAeQXTjLiiEvWwA4IsY16t6M-q5VoxMPbkjVMktlrDyjwDdP3qot0y-FHFElM-FrsG7lmUvR6KsACV_eDV0QTCYhn2AlOfofIu-FQDWNcu16j2bfUdSo8eefhYoBoYMi8zB5uw7IsTDYSZwn5AsxAWBP3lt1guUT9XiDiWyB2d6MR5bxjzWwo85urHFAXVMabax2x5nHkm3AS6AzFplWkwJDtW0ssM2q--vF6x99ayF3otPm888W2kD8j3-yGbXWeW-3k8Jp3AiE6nTB-OMMaQVtFjfFB0n9BhPtiwIQBPZlBfNvq4GVPRuXQ0W1pRqYry4O0-q1r_au7JQJo75Egk-0FuKdZjj7t6FZmyFfqT1c6sb4b6ZdMoI-z85ucrfj9dQTHaLgImoc_xXfA8ANJsJF-TT3dZvqNTzPfoVQaYyPDvWrMP2lnLEbeynooombgFTS-XZT8AGzt8rYPY6ylLNy7nqzMw-gB-oG-Vm7tnCFg5spYvMIjI17IXKKLnKlgpo5g5dB5XTW0euhcWqHH-bHOviOsetuN8HMFYXvR83xHfbIa1FwZ0Zb5nSTj1d1c97UjcZUp8FI_6lsNmOi_hNhlIImrSjE8hxc1r-PNjv43PFoZLH-T77tArbD8KyRkFA8Oix-K9hIhaOMdGJ3a65JslsHT_xs3_fjJ8fBRLLE2YG-HpsEIzGlvDUnNBSXbHZJ30IEdwD_coVwPwdSeQbpV3fGu3ac0ygXzpmd_8bGOO4u--tjZLY-HuRPEmL8-fbJcgNJhb3AGTlEOmhi4oCfFeH-aICAN6QzU93dVp9lUW_C8Z4xim9b5YOjxYC7qzaMbEa0WUWEVuVOUpKBR5Z20Yqs95oDZM49qktCi-iIcRKnk2bkez6woCN7jrobahMyq_xoffo4KXs8q_zG4UnDtWEYivzjGBMWOuKxMovZWuwWHH4Bc5Qj0Eajst_CJvIZ0r3z5SaBIh0DN5SKTV2UQGl9VlFt5051lhXklCJh8FU2hEFkzxMXPiIwb_rbda_Rg60Pz7u-Ua2KOCPicWczGzlhoOTtggTajQptoYhvBlFoFVpoPhNNcfwC_it4vLP6g4IHdgNyZv-Sc0cqv6VqJaNHShCwmMZwte0tf61Df8DyXYRGmZgOyoKQyLSyS958Ga0byA3oV34t0r8DgbSyR1822Qhoi3Z-2nTn44ElAmxeQMe9W1TZ2NjnDJiovdH-zxiRRmSKD0JQaprEOHVys1OsPPg2DXn8VY-p3tpPmaHWlSH27lKNJnmOv6jOJ-KC5OhU7DifPJeAepo9xOhQ419P_cjTCBq1HWo2LdyyddIAoDjvBCnZDGgoOJa9qFxwRdiZq5wTEn9FiMRCUBLLnCvn_AAlP47_IKfZKGHu_w38pyww9RypoaXSk_IgNtTljI2KYeGKcyzmEjBdXc-5Xwfy0decU5UoF13EUkJcMM2AVC7yO-WmjqM4bdYKl3j68gO_BlZXVqhpM_FKz3NwbWoL3LRl5N19WUWShy1do20AEYrSdwts1PfRsbpCjl7zgNo1ITwMlohpcIJ_aUq0KwIFkn4hcZmkEaux3Obb3nxNLa_yYcV7HZPDvRJqJLTQ6FykAHOK6T7bpTe63OBV5CtW8XuE7DsZ2mVYFLfJTt0MdlWyNgBl7dgaAvyn9ncMGp24IDVnKAnvjBMlS2yWzkJ2YX3x3hLIQ_TbjPgoyo5wfK-vlBWezGH6eKlV44YxuBbZ3xJF9oJFNmaMo3Avu1zJvkkGQWGcs-E5elZEUQ4wsdSa1Jvy7Jfzl1nyreAVj6ng8AUc01YSJgdKlgdVDBV-fwPmrI6x0inTODt8IDyoblTAUmmpb8xWCT0LnfuoTFjaQyKW1_vKrOkQbW-yh2Ezc0iP4sd8UgzDFOzVsQDUMXRnx8X_hTAqdXrbSha9zxYxg5qQUeoE38lBNXAMnTvA3LlRGheya8zZ8ErJR58xW9wdTpBLBrmRNEZbZYarQAYsPHurNSdxG-HkyoqLqnlmmKRCSKX9I5e7JKSPp08wpY26VXMJH9g_CiuESvWVHVHC914hKv9uOcVEtJXtyKYE2_wV9KqQC7PVKYY3HUM=","summary":[{"type":"summary_text","text":"**Analyzing client-server buffering deadlock**"},{"type":"summary_text","text":"**Assessing thread overload and cancellation delays**"},{"type":"summary_text","text":"**Investigating delayed WriteFileEx completions**"}]},{"id":"rs_0ceecce325b7fe2d016a59755f1fe08198a4bbcd98a4338e28","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXVqCjJDMoOtbLNmEPF_I3xuVF0s3p877d7bgCjRMmixyp-pWRXhEDdTWb2TqGX9NyEzJkYp6xZ6yo2Eg013TizPrNjEO-01gZU9ppL6wPOZ39ivCZbIBLnHs2Y_Tw7Xoqo_4xwnnMkyikPBtJIoGySoDhe18ZS0O0r3_EG7xNcBjhISiFM7EVBOxkqIsb7ts1blVaF7-Ao25bD5vTyUPDe_jMRFN-g-Hc1RIiNqsO4kLDGi282fSkRhU66IlvV5Ygu6mYXmTxAMVc-oRRFzG7b7aX-ksz83bQzLHPmRTVkAoQN81MftMWUD3JNpuVlBrxJoI8h5hMLMwIgFbhLB22IL8ogof3RC5iN2g20bZUAjoWDFCn5COtD0n7TI0kZFvJo23AWWiWRVaa3u3I8Ucefka7rMERQG8vL7dcSI8tD13d1fMVueGUfNnV2vzxVQu1zzakgmmGzpXz3YYaJQAR5BLFjWVtFNxl447pC-uQh5wwro_21goP7s-HQixAGcYPMPWwLSmiLBmbVQldX-9V9B9p5Poa7nQqQEtbXkdKSIZhqYmsHRpKpZGM4JrAxOxGb-dRjAmavhEOXnW5ARCwIkIwjUfEgT2tcXpsGhG6qmugv8rjT7SLH3y-a1sDclK9297cv0nmmvgR-Cw6WgXEIt6x3RQTBlwJLX-2kIy5EIZsC2hTsmV-eBJyqxmUCUxguE8Ck86KAOv__yFstKWbxvIw8WZ00xazVlW71HOu1GqWHA6ah547_NAKliPgyvX_WX4UMA2JTuEP7CS0fwt8GBYmawETKC5sDQPuftiT5no2ccLRMnutNgqAw6Z6lOU0OZUiI9wUjPU9K3wQFxiYtY5d92m48lldpj2VDBq3s7v5S9kwiOs71tP0VyDrW68WfN_EWUZew9Vg9MxhswCMGZJPnCI4h_PkO7i5tb0BrsHB1-3rWzvxDzlAPDYgx8uXjTplod-ikzX5TkVCQPp2HKQtIZFZaVPjlh9UuvjjpOFZXJzD-pDghq6iry6JoWzoSKWkKgm0xyDaeUB28NtiNz-RNHMFy0L5d77I4ectUSRjbL0zl63KzNXG3Hgr-_WmR54vQARsZYBZR7RXx8TOWsFmjBd-THSfRsfhupcMo12BPDJ_1wdQ4kv9WuOr5i-rsI3K46LzMh2dJI7CwWyBVmWapX-VKmo9huuRbrPRv5TbLV_YEIvbWsSRQB7IwwGMkWddK0vPGmm0yRiQK6D5Sc9LGtxVPCsnb7K6wlYb5vPRVtrFsSb567k669QUSFHTgZ0gG5K8wiVR1njYl9plaon9YI7dn005cX1oGobrDHC1kcBKfTAzGczT8O4_tjFkJB8Ipv3T9fUGdgb9I9VCpTOu-Zr_9z8h-SxY7IM-ziIKHOspy_qCcs_Injt5wAIxEnyScpUUrli_0vUngsupezzNSBQqQfUxZS1QmxtYYe6EB5yLLe_9bU_8wcnL438RXwLSgnTn_Ldm56Ii2IBUKzuFQb2UgISRBUo98oZSOEuWySPrlYzdsi46pvhFJ3SP4WlHuP06KSvS6qPTKwJZAkPsZT5g97odCKxficc3LSsZrrf7LVBntVGU3e0d9Igtt3IjdwHvrSEcL7rYPwbO2EZwqDA1grwJF5Rf98zqHMtxrSQf6ej0SnZV1Aarny9wDBRSEDBZnVkjenYtOcTd8tILCyUtynm4cLWNmYWd71TCSykKgjlCgqBva5bNI7PkZU0NBEtTyg2iVKMAQMFYWinJyNBAwy61J9qo9tB1tlLhh7BeTmmuiwMT-u0wZXUSbJiuPE7ypwd_chitXZvtLVMrZf__y8Mnx8XRDC91IlueD6Z7ZKbqxOUhcLAyOJs5IcYMQBi8y5Y04PYniNLkMM5cKsHWEiKztY79CTzxPRRwXHJuFPlaZ2elPNdlJ1unn48yCqQ3KyNJxrjlvVUkXOybTkjYUbBqLWrN0dlB_OfX1ZnfKPoRSMYpvtP0TV81KhnRojr7osjZOzSW4Qyh2p1S-d6TeXAu6yQINn9mF0zl3pfl-M6Q2dX7NARTfKpiCzp4jPhqhIfStXcH9Z0R2SGrZ1C1MVONPn0OpT7Fg0aBpeZ9pWSOuQbjgWr_Q-juOBGcqncIhUIT__KQQeDu2ozAKmveIaxWTKxeD80Hui-olubo143UwBrvOZal4jysQJvhutX0b7l2ouqauq69d-fId42p2zAze_hD5v1Q4sbqcToKshxzcGR9ov6W8JGGMsGTj0Q_HGdemt5kzIJYMS0PEVfSW5PNOB2iyFVEnLHvvRRHrIbmw9gvzVfh7xMq1mqIAqvOGYauz6HQZI0PVOUwAATBMhruzJpWGkBvQDCP_-TAjERovvHDpVupRvuicB7n-DuD_yEqxPxCgHlD4Z8O9HSnGQAtB9tVO4jZrAEt7JJLy9qGPmoubttTjQRS8j2qDJrYYl_2p1Tb_sYiYnlG9mrkwgo_t6cgzsLS-paeCck1hOol2FZlYnpeJm4maAb4zQ8OwSiylu5r8a2etPIjXFMpoP-KRKbzFgkOvQjpYOxWplw9CDYO7S-v0pb2PI9-aDViOOzMou28PkMxiLbsz8t2pMf4d5BiOAwSwlHTvUGAnAdHd4Q613yOtTFbXglKY2RqG5O1qqLWg9ncBGwo0iKTHu672n3oy3yPuEoM-AmiDA2kk4xWG-PbkJGdGK5bXE3cjCi2InAiTUjzYmiqvC9tzu6DMuKRFcPaRoTIffU3ZmRwII1FRloV9ZVlHQ8_rYmZqWXTpztQkDgGNQsMH0_3Wj27l6r3NG0kYQqgecO6wkjQ8tmsk9q6qEByRU74hG05R9zpmrN0Ecog_Bgrw8HdsMhZpZex4w3mwaFlyd2FF7e-Dav75BjW1E5lOAlrkdhkY12PL9sdWdGIFavOBa9fYzvpCvW4fq3QQ-pVl2aj-gHAcCgha5XyWXxPfLuwxtCI-yAabio8hyQj66zyxzxvQZM4bjQPQSvC_TvpQVGUaBGa8K62q9zWcCXcY-wydNOE3Wr_we6h8eyTgUau4f7F_W4tbVP-LFnO9zanEv9clcBuS4eyxUlb_IELk-R2pJ9ziFVwPMgbXvAKRy9hRJzxFPTsRHBthwu4oWYLTJCu2nnTLv-srp7iWDyqEeIn1NE-kPT3W_ylFrrMWReg0GNBIadlsNENncmGgG6G52deZ5rLN8h8brboQGDxCnDKOH5TDYqnYQgz_QZrDaj__jzObCPANpyXGcws_fLe2tMZd_xRNVQFvFOxyRwHYxR1-pDRwthY5C6xA6wmBmZteCUkhdd7iW5B3kPUPjUuCEfr3Zq6NSMztdJJfWr7o64Ng0MVOYAjQsI1ljHnrGK3fxtM-LxLlqPdyF6WGrclLfrl9NUN_39N8kJIubdYn6H0Nv2sCARdIwcZ0jttSMUovyqQzrvnNmYDaH_a6jrHrnzB68mBktB_7udg7RJJoewBkrH45IVckl4ObBu5h-aUi-Wgro6izT9FFyY7_5MBQM2lH1dpNX3AlB2GpgATvIaOfRutEMGaHQiRSTfu2B0Flc94v8Rd-djxIYxZ5XpD4VKyAaEm6rveRz-SV51de3Mzgk6XW9oV1U7boL2OkBiVHdC7KDaM31BFoqd1olZOZefMxGJ0CrjNkOeZPsAavw2J0uI3ctZUd3VQ2dXOqRlaNiw1rx-nWBO9xnTAmTHd0LxetEpQVdA-wGUbIGegmho6URKZ11lSabdvhFP1eJnxa7iNbPk7XpHrEtHOdAj_c3z6Y2RO9YlifdeEv3qwYj7tCWMvaOMK7tJGbdeXswdSkG5pWyLzNn3pgNQdznvPi4K9CtinzJhcLjPjIwCMl-UyNkb87aBkhLfojgE1pM3IprLRpUzaPyWOSTuPaSpwx0ZpdWDNb0f490al_l7wYEsd095KXZcObPbiu8qwBUX-lQ1DiUAIUQx0uPAWETzRjsYf6RhNE0T7GHd9XLgcD_x_7PVWfDXTzdNEpQGh_A-gpZzPxLLdx50lD2hEpHpPctU-R1fgSFwj2KcZjX8E_G3LGNNEMYdT9Urq9LNnD1iih1lvMYk3qdCRppqsfauZuWrdOo5rodOMeYwkM-2Bex","summary":[{"type":"summary_text","text":"**Analyzing write poisoning timing and locking**"},{"type":"summary_text","text":"**Tracing version regression in connection retries**"},{"type":"summary_text","text":"**Classifying stream roles by IDs**"}]},{"id":"fc_0ceecce325b7fe2d016a59756a5f048198b025205f1731ac42","type":"function_call","status":"completed","arguments":"{\"i\":\"Reporting field causal mechanism\",\"op\":\"send\",\"to\":\"Main\",\"message\":\"Causal tie found. v0.34 W1 makes Failed claims retry at +500ms/+1s (max3); fresh dispatch has no concurrency bound and thread::spawn's every claimed row in one 100ms sweep (dispatch.rs 299-361). Each Attach worker net-stream-subscribes then immediately sends session attach before reading; broker nethost StreamLog::attach synchronously replays ring while holding that stream log mutex (285-351,1676-93). With 4,361 workers/attaches, client worker can be descheduled/not drain; broker WriteFileEx blocks and poisons at 15s. Poison log is NOT completion: conn.rs watchdog logs/cancels at deadline, then re-cancels every 100ms while inflight remains and severs only after write_frame returns (345-399, 540-593). Ordinary Brain::Whole read has no timeout (brain.rs 1734-49), so worker/DISPATCH failure appears only after sever/EOF; variable +0.1..47s after poison matches delayed cancellation completion + 4k-thread scheduling. Direct effect is attach stream/input forwarding stall, not ConPTY child output; per-stream mutex means not broker-wide unless fan-out overload. v34 didn't add 15s timer (pre-v34), but retry converts one abandon into up to 3 15s replays and pinned-opener/fallback adds IPC. Finished Attach should retire before subscribe; correlate poison stream IDs/opener family/finished flag. If 4834 is active controller Attach, strong direct cause. Fix: bound dispatcher concurrency + make StreamLog.attach replay async/off-lock/nonblocking; stop retrying deadline-poison blindly; sever client read at deadline without waiting write completion if safe. Test real 5+ active PTYs + thousands stale rows, production 15s/cancel lag, assert continuous input/output and bounded worker/conns.\",\"replyTo\":null,\"await\":false,\"from\":null,\"timeoutMs\":null,\"peek\":null}","call_id":"call_05GlrX2Je8d41i72mspWnIeJ","name":"irc"}]},"duration":38812.80330000073,"ttft":4378.950300000608,"contextSnapshot":{"promptTokens":78042,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_05GlrX2Je8d41i72mspWnIeJ|fc_0ceecce325b7fe2d016a59756a5f048198b025205f1731ac42","toolName":"irc","startedAt":"2026-07-17T00:21:09.080Z","intent":"Reporting field causal mechanism"},"id":"074d0050","parentId":"5f48a681","timestamp":"2026-07-17T00:21:09.080Z"}
{"type":"message","id":"3f8a0bd0","parentId":"074d0050","timestamp":"2026-07-17T00:21:09.080Z","message":{"role":"toolResult","toolCallId":"call_05GlrX2Je8d41i72mspWnIeJ|fc_0ceecce325b7fe2d016a59756a5f048198b025205f1731ac42","toolName":"irc","content":[{"type":"text","text":"Delivered to 1 peer(s):\n- Main: injected"}],"details":{"op":"send","from":"V034ReleaseRca","to":"Main","receipts":[{"to":"Main","outcome":"injected"}]},"isError":false,"timestamp":1784247669080}}
{"type":"message","id":"14e2e424","parentId":"3f8a0bd0","timestamp":"2026-07-17T00:21:41.415Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Designing per-subscriber bounded writer queue**\n\n**Planning subscriber replay acknowledgment protocol**\n\n**Proposing bounded dispatch concurrency fix**"},{"type":"thinking","thinking":"**Assessing pipe disconnect semantics and concurrency**\n\n**Planning detailed Windows stream regression tests**"},{"type":"thinking","thinking":"**Investigating release commit discrepancies**"},{"type":"toolCall","id":"call_p2XvYfkOnQ7LyfYr4mnjP0Kh|fc_0ceecce325b7fe2d016a597595064c819893e40837616514d8","name":"read","arguments":{"i":"Reading release heading guard","path":".github/workflows/release.yml","selector":"135-170"}},{"type":"toolCall","id":"call_wQu5qXGOIfPpYxrrsHAnztLY|fc_0ceecce325b7fe2d016a597595065c81988ef29b22bf2b3781","name":"read","arguments":{"i":"Reading changelog incident note","path":"docs/RELEASE-RUNBOOK.md","selector":"40-62"}},{"type":"toolCall","id":"call_AGQBo6u828zvoWCoUVAI7D7G|fc_0ceecce325b7fe2d016a59759506608198a59bc876f5f3685b","name":"read","arguments":{"i":"Reading v34 ADR decision","path":"docs/adr/0038-broker-stream-lifecycle-truth.md","selector":"1-170"}},{"type":"toolCall","id":"call_adl3PxzwiXaWSTJdk0bJnkng|fc_0ceecce325b7fe2d016a59759506688198bd1eef40bf5da8c0","name":"read","arguments":{"i":"Reading retry timing constants","path":"crates/spt-daemon/src/dispatch.rs","selector":"78-230"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4267,"output":1342,"cacheRead":75264,"cacheWrite":0,"totalTokens":80873,"reasoningTokens":1179,"cost":{"input":0.021335000000000003,"output":0.040260000000000004,"cacheRead":0.037632,"cacheWrite":0,"total":0.09922700000000001}},"stopReason":"toolUse","timestamp":1784247669143,"responseId":"resp_0ceecce325b7fe2d016a59757556608198b06360482fdba1c5","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5975766bc48198acd2f03b737add2b","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXWDQbWD2g5zzwPHyxeVRwB91waE9Dp_cZsrZ7QHmyyf1B7IfKl8NLfQRCaEtj2Mu2ETgYvmlDBqtrZwudB7wHjkmyiVhjBrvtQGcrJjTjcVmAjWUzUErUJmsNHEQ6DRsSp0ofk28B2J3EDOYWqW4KDT7rhHuzUa--K5q0Q7QzE0chG61kFVsY98mulvDxiTVeTa5bfF_vMYBc3233poQMJ_8rxmz8GhAN_twzK3L6xL40okADDTPMrGggr3wtUGdKKP_IvPbz75JYRP8wtBYY-To0qWS9Z_3qKPizqiqqVXTE-c1tfozsptl1ygHMcTIDE7EdXr-Z9NMsU7OE1sZz_Gjy8wULMTe1olCu1s6h-0778WH6BHKE2yDyRQi3JfeMRbF-CTFE_DpLhS9erRUqfVm1A3E3Jh0ppAwHQ40WW48OhNneQr5pfsFzBzxpzIEqY-rKY673PAAWgxRHO8SK0r488K4xx-t2NTFd0wnj8ha0P9-mdO3xGm8PWJZr3GW0L4WM-eOuELjx-qamckvwFG-BKjxSjGPyyfmoXMRqgp-KUbOt7haAtXpK2jjtoqmSAJHY0wl9UfgrFKckH6Wp1vzb7WsWG3KPjBIN7XPGRIQdJQcWZVqSU45RFJ45Qmzi86jvWuJNq7DWuo6Rn1Ab9r6_YSvoZhuLjSbcmD8YqvPSD2hYhlneHfKA0PHqoH49pUM1FGa8LzHeYq-AQdLfzXwhze1WhTwKDqZG5BtUej47qi5vmGR0PLXqQlQng180Qd6Gej8xQEZZXwKiSzcLo6y9VIgROIPZ8X2YdJB73EcerkJt_JjG23qnPHlwBbhbcG9Lgt0eNO9s73nrtSVbgKhHxvVHwyebnAAA57tL7fm7KxSL-fsQvKU_9e8FNH2U0oqh3AKA0UW4VzZrCt2eQneeWm2jjEVTxguxFrLlSf_M-ZMWWT9Ds7NaG2nVfGp_MsJ4sUn9_8rbp2omQzJNJ-n2Y8Hnb7Df6ab1_z3hCfCdeAczzqT4eQJusacDTadsOEPFqwjFyZe6_kQItrtL1WQCMPdFWNc6Akd2eTCMB6ZxrFDUH_TSDoVw849jiUft6r9VxtJskF0YJGyU9X7YncsdYjHSbL3dB-mhir_LsosKjGjHb15StkRrDM11uQIFBIgivh4pskjfO-RBd9RYLVpwTGqiKydxmeHzdXVUyrFJt3h1155_Fxfz1AgIJjfAz4ds3PiTklQr5gkyvc3ismok-YADLXyK3Rf6S8fYRLE_lanlb7S4uAjWshJgVpO5c7v0fTFDaonvr6pPtDlFoYTN5G7Z4S3z0L355z1s5kLCUu90U6yUrxmsUzM-p9R8Px5devlOedlhqYdOnL9OegLwMzXs44rtwq_RoWwLjaGerLAvYfmxV8UE1_kwwA6k3nYENDw_Gurp14ZUmi_KvXgr3mA53SCyk-BzWRT7NTTIWkuNydC_3kzDJDe08dbrkPGblv3Y4r-3M_1zlofnANE3BzvA0oCdZ6KPTHOlbq8Sxh5edDPWqA3ReiDUD-3gQ0FzESWx8VISRiofy2YugCvb8rGL6mDGk1rTYuyBaz1g3bTrDOhAZW7trTyXpZmkcU1Z-4B9elFLSGHYJtrdbwum-9JSdLsH7VbzuCj-fiLC-7Q9mxAOPCqxxyypGs3jYY9DIe3MtMuDSC3fw8eH6QsvVJ3dAYQdJ2GyHzvc-TbwWHhtA5tPJ8OgaJ8eRVzUUI87MVprv7fuP0rgSLk3BmBzfrKxAjYrw7ZSYRJZRw5bpoctJ-s-K66Gg14QvjNwLOQHF0jQXiHVk36cZ23vmk75yl5FeF5GQorkIALRlvHcRtAgj770udIBbLmcEwWJ2SBw2ub4Yp6wg29hN6nYJrsm8G2YufROs03UJY0mn3v-pfyxLsBLPGnT5_Teg2FLFQZMpcaAdXYhmi4uOZhPl8_Qk44QMoMysqYardAT3O5Ph2Xf4Fr5oHSu9qi3IqUpTXSURyfVYouexF5cGFPC_lFiIqFuMlayq-kb2SIoCNzbHcSRxDbtmieR9ioYBQtPOkM9DE_cV6lCGtPQ99iL-_JPaLezAWdpRPRjnfZlni6CrEmR1cb7vuEGqSZcw4IHHN97Oy4SWFqDGkd1unm3F_kg_5e4Jl9mELuCoUJEkE88IEZX2RS0p3ksk3wxvDHcMYS2YhD-dS7usBS2SjLW_6nljE4ygjNAkA8OpSOXKPSgoYghmCq_rLGenp9PsYmCsd5eantfCm4pv8XBS0UceXBwiCSf0i6jaJw7dZGrp7nVY_4P7I5NZ434uZ-g7jXuiAnjHYB5aE-owscf-_OT6Oozaz9Zo6lwFPmEqOsVPS5aozDJ63FZS5UsAmq8CmkXsa8aBcJXq53cwgzDLYoAdRQc6rNB4KOCPduWV-sSXP4EGV4yq3pvcCfyj4iiVF8B_41sHZdVxQddqe7PdVwjewE5jil0EVt3CkxfFTJiCdt7VzhExNUUCWIfPMB65IVhxstuvLTwIzdO7xXK7C10PdksSHqHA0ssWRVdLY6_3KxTpkxYwBBkqbv3BIClotUTK3rBY4A9krmQrJlnqW7KlG9gJbpXkd0Nv3CKbNHSfLn6ytjN59_objDl72y3lxHIAcZvgT8Qw9iGm0bajWofYzQcklr1845rkgrwKZR6D0iFJoQTNYBIpUsvWKbAtdiBs0tVfagBWRpxj0rOx_KcQGvZZrIas1DFgz9lKZcbkcg-x3Rde0ga17PsVf-YDvXBc98D21Vik_7IKKkECWcLTpRFWqbe74NgAR6Zxkihmzo8lt2afPJ-mr6RZGlTtM0op_bzdc1G3oR213xCUJjnkuyChiELA7333tyqmIekdPlElZNjhBep_H2GLPRCA4_vyZjK5bnuvamajIpjnCw_0f8yJUcMZdSY93GEgxwGO233gHJYsTLi3GN6pZLFBkBzPMJvwvuWCuYLC-8MtqkMteEIuCbUIyn2R_5O0DqhOJ4TKak2CJiDcPcX6pwuFUAcJh2-c8r7zPhBigf8VKM8tghenTAvxkM4IjRXf7ToevJaYqUkLrnDBc3CxuA1m1WmC9QvKwptdJatnWBr1yna0A4ABmleYkHstZLd-63sE8dw2RXi87IXW_6a1uNljSslE4T1utSmQNabhn6jEhiVXZVpid5-RwWvgT5fpzzsQGSwZRfLIf5wg9hzacnx_zAzgOBCscYxbZ7T7k-Ry6OJoY0dgxIiHa1uxxpYPvFM2SLF-aWUGDC1J00_R1HtaOHUhOT7GClL6eqrzJN_QftsYkCx1ETkE0A7WKSOpt2kZ-0bYjKD5Eoa_6jr1u2aU4Mr-Ww6zB_Cf3kVMkMZ-x_xOxE6ibH9ZmG3xFvWsXe7gx00Ob8j-LpXv3J_Rj0S6YIUjEu0HHZVUXAxisMmsrAaJgmTenk7gHTMOr679_E08dmysQe1WfXC_qt2aOHjEfJZXDy31aFzAQxam7A7ZfXQJucc9L061bkYZraWsKR7xMqpzJJo9--n1mQg5vKuoSJR2cJ8cgzLDUmfVgMFF0i9fk850Mv24OZxFFBbuoolUbxEqEIBuQGA-JjxciGdLrswK1hel184uE98z5x7r_cfHDm1Wa42Gg9akwRlzHNwxUmbLfc0Y5QfmT4d8pgwYzjPh8xEdcIBz9S3PQffATOs67T2n0WrjkTDfMW1IlL5HwJH_dysBXVQtg5PEpsQiJXY82bOQeztenekurMepU7fd7dcz2NCLJQ1rjYOI6qacipKlIOca3RVVMFHIiK7o21E3m5rPL76628_J7YuywtwpgM6ioP6pOqq1K78E4wZyv_gLSBKg9g0EUwgse92PTnKGDNE-wtZBtDE5Ytkezwcmv9pluknjlO5GOFmlUOOZbARLC7A732z_Ri84zjvsJgENB9X-egzkaj3pszTRXlGtMN_Z4CK6gaif9fiW8I5kzrAKCAWvZ9w7_aEWdQj2phvDMm1OlfErAPm75cfcYk65Fa5ERNv1i58P0l4yddqwn9yAjWR4UiMjdKjE0OxbOCgFON7wFumufRbVz_R2QfZBeqYyrq8jeJJNumwcK89MnauIhpXdKIDW8YUz8gplnz82TPFCXbr9I_z3QMV7-xeuqDclX7hRNuIt0KgXidqrfMt84szUwMi3c2dx-V6EGkGIG6Gy5G2ZwdKocB3FhTGmG6iklpcUfs-T2H9yuU4B6IYyjBI2f7X3cqgRW-AgtZZNvDQMMVAGZw46B9ntruTgzgW6QfeqNR5YRhWZqQx3eF70f0JsXeJCNgk5pyVzkKXTbn58nxV_i04jf5uVSMEjsNidt3FhyBOQEQqvDYAZy6fXsEVqWhHslgj_SQleQvjusbSnArbKi5T5SbBrDdonOCjZTjwmopKMwXIMh-l9TiTE=","summary":[{"type":"summary_text","text":"**Designing per-subscriber bounded writer queue**"},{"type":"summary_text","text":"**Planning subscriber replay acknowledgment protocol**"},{"type":"summary_text","text":"**Proposing bounded dispatch concurrency fix**"}]},{"id":"rs_0ceecce325b7fe2d016a59758373e0819889349f8684739a3d","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXWQMSMDt31SmEsWAD8tKTc6r2no1wCrH70ivdE_IFWMlWaxCkYvHMpl7CUiZrLZYwAFkBDP9LnFIdJwm2DJaS4S4SZFriCvRr0hAx9tQ7c3GuihQVGnPkKkhApy2dNpcGXroKcDnnDLBsKj3-PV1__C2NkXZd6grab_JT7mtQ-b3p1bd6bb09PU_qSL3YH7aR0DmLhbmdn5Xof6_JBhS4kZ9B_b-FLJGNMskyZe_H9jYKcIrIN7sdabRyj62w8-hby27iFYHDc6y5cY1uwC7rkJZX_r3ymkDLH7xEhpe2gpoFkPD40mL8W0UkCrF34_PjfL5BFCNsDChrHkYIEl-Ku7JjwSBB7ODOc5FXRwQXeZuHXI3hZWh-esUydkAVwg6-BOrWEZnmFo8Cyi8xuzj5Ibtyy0kTepf4oSQ5h02CbEzNJnPHryxn4N74iBbTmfaVcGbMgUHSqUWxvO4r81qA52sYET-E2xpCE5t0t1Ldkh7reug6l3iWiT44m20Tq3st8PSfztHY6OSovLjeBP71PIZpUdNam2bM4Gea-Oxa--wx48s5VzW4BxKAT3G_RXA45-OrC8XDsJlzhoaGDtWxgCTNAM3jTALqNDPBRkPel9qUnrI-V_2W_XWKlBNUE5916Jeuk6NHi-wKClY7ZDt6CD98XJXdD9E8s5c5jdFmyc-S7qUweLb2audgZCYwiRO96gHv2WVjhxGw9GrOtm-qQYAmvKwpF1Q0SJNfDTPm412x4Ufia5lUs0_utFfJvgVpJ-gfsIXWA4wjmHG2wUI6cz4RBmNRo8LjDZ4nl5LvLvvNITAOsa8GG3WZ7-QFNBQ0CgqkS328MEV_dQoqFtQTaxr69jEiHkYA6LZN8MnGEQ1tiPPinM4QwgaiGOKQ5EN-v-GsKgkLCqjz0mhiDl14MaKTkYt0JvOb0vCN49vSYVPdfikucBHevu7AqKzzlhJ9STjF0FQCtrDuJKTccHp2N-NuPj9cKoxkzYhP8Mbij2DeeNgoTe3jIKMKKcx1BxdZrTbHhgV435qnSrcbyFM02krhZZE5Plhb1miRBa6Hg78ZdbIJwuUr7WS17jWKPgyeM61qavo7acfoRh6X3sc4Kqn-72sAAM5cEPiuPB-9Dac4oATflJhjDjMVN2j3n9KN43ViIVVuUi49QAYoMfu0OlUO3zCtLaILKVZupgE-EvHNxCcVKTlvqXit-qc9Oa8szhwuZD5OGBRt3u_3poXy3LpOLtF2z80Vp7NGM9W1bv64ToFnFiPEgT_7KtujxyAQ_-Ch0iVBIPoCM_G0LGPmUDtbkZA1vANTnPxeaeM7PdiAhC62Nl2QtoCRkJg9UgVRqtnqZiJ2t-ZAc_kfjbkRnxf9tS9rL4Y0U9Clz6GlFeonD8X1b6oimf8nV8XJLTkK_zM-TWnLneRObQjPb7LTRThJWLyHabQ2f-o_40VC7NqjTi66B0iEWnoN-43n9fn4b0NzD7K7_OAwDYjy6k6RbG65ItknHze9xnEEnh_eg1LFbQTiuSsgagM3dJwWwbdEQhzMXM_6HXmSUDJbTTPtGcG7j3jDMq73F7qRREB43olagMcr3lplY2RiBhilPn2kZFYueILn2jc5zFuPsWrksgPxUuc44lA03u2y-QM0cHfR_tXSxBzT9emVmafYM6NdvgrzjzhK3nUUS1-tqC8Vo0fl6_p3JzkfNq8O0S3aVxOgMNZHCCp7OM3vYEeSMGZeGmcbljoxbVVZO3ZyvUNncAXjPI5Up841bKs0Ao0v9QVn9WJGIoFdH7eLuSADban6-q1Pkob0dh1PcJdiw17VdpHmiv0kpPqs0tfBgw75QmxsaH-pwUo1ybDC9CM3xxhen6nPVAxNMbdbGbsWGwC4cXhmY1-Xn4K_Bim0oAOUTB9Hak0ylziYkVxfJMlfCD78rH4niLBS7eUetvdCqCatK_Uk1nNl8ApKoSsAiI_fwd50RStr-LZh_9vhKvrwOFRm8Sh9ZtxPCuCkNN7MrdK8Y0qn4ED41SABezVkQbMPhgowOHODt3wOcWrmbCj4A9CfNx1Zyg_UiDc6LPGXrSAIOhXsEDy9GAFcTFbdeMT717kpx9fqo09afcv708tf00YYH-cOSmTzU03lJOCX6Pa7Fco8FpIz7o8gixlRNScA6JmQFmPJtolNlghB0doMBGgudRC6Hu3sQTmko7cR9VzoI8UjhsitfVtQjhwKxCcfKy0k_S-lWdERTkVcYyIUp0QzFKAHVYNw2CsId3bNxPbVWw0G-x9Hn5D-_pz0DbfnX-qIkyeO4MAL3HA-YaDbg4X8vZKIu613GwswaG5wMCqt_DKvnVbast5m4CYQImaEzbsdIqK1JZKh3OjAAN311bYYXxz4uHTu7JGGoj5TnhPDnEp_u-e_mpcRtplQcR8XSA2nZ7lYLSe3-TJLLgx55IHZGZb5eV-hiAUO-g7YHOR1y5zwl_eoDJ7RcaxNTHFd0-kDFgbORwv6l3TcI3csQCJeDZWuwliYOwKuqEUPxgtUWwDP2LaBiDUT7OQLLkr34Cwu8p3JASArPgH3PHGKI5f1NGGaLeEgBxpED7LrStXqybjDOpUPZcdC3xrWqEpWPZmaQ-FEIAr3nCG1JtuMuXp-Ou9jbWV__bWtVuLpYadJIkifaE3Ku8hV4S2ZiczYAdvu374Rbeq_Wb0li_IEJtc0P18jez5mwWsGKgFiSwol76SSBSwb6x6Vf9nELCqEQhnsz26Nt88GfpNfQ3Ib5Y6L3PURw_OZ_oQSKaQ7dnwHe2YnVM4sVLucVHDNdBw9YOIN-Po7QiTr9kaxLaAw7S1CKXYKBmbT3dSTNuFaS7qVq7HnkrnFl3VVUzgl1Pv-AbK6bhf88Uy36aXKE9rcbgBk3oFhHA5Q-yV4v5qt_QmKm5wdp5VMMPhASaqMLB3H7UD5Rhz6AvKX9Wky1vudlfv12LH_nYEYP4xc7Ds4QEgA4DZY4bJE5V-JpAI7Rh4u0xAFOqA3Wt8pO6l4dvNG0tFBoFbm7ucAu-ucMPOjJRprEKkgQlQaRNW5RmEucRJk3cMbPSP6wEgstIZ7KzZmFducvZ1MMCQEjuXx5lBBdonF3OAu97XNJ1FuaoVh7D3l3pyBmhzQRlpYGv73_s-hEZ6Zl4LgOfl2_d1iavrJG0gmnHvZAGfLnJJ-kPp4XL-NL2BIRvk-i4nZswLFtZKtzbx9370YdljPV0uFfC4r7JvyAHHet4W577GzqMq8NJkestpuW07_6DtTiD3NUVSFS5nlFNLiME7PWTUuge8BwkFUcO1uSiaxwCBJ4MrIqHniFexGRqCmeUAoXR6mqfLd5mbGTsMOuZbLJAqd3sJgve2YO55QqYvldwRjyzhl-MuKMhoLR4TjR69XC4vw7U3jPOIm-W4Vgn5khHgQcmlaa4z0DirIvuwwxUVMo5PJVID7qaD9bJ5-1wbjzfFPf_IVZxzH4AyHUSsttTbqK_3TXwFsueGK15iCgyb-tTL_ivQXZoMICRnAUgEHFbIhBzfuw40jlVaFhO6K1Qe9N1Fa47uXmaN6iTZmdfDd7bXRAZ-zA6idnEpojTpb7JDXgrAvpy14AN-Y7jZDmbAqFsVqDQtF-3CgFqr6y0ETVGhIpemG76Kubm78Y6XGBuHLN8Pirs3ge9AqPQNPHncu9V5ixtPfzZfCNhDJWy2UWOxhw7VZRD0kCsOC8mtkcrlhJM5scodm3hy7at39PLJ91VekziBPgwF6zaTO92zYfu-1uyKicU-1-XpFGH4O_QEaIqoTb-wjbmzuhSPyMGWzpMyxRlkNRZTxsdluFOOHR6X4_Bk7avXaZU2CsSuWrkUkUHMvCQfezy4kCOHY-GnzaB0nh_VInagtwSJQB8KfkSgbW_pexk_JoqSRo5W3MitR41LBX7tiAnnHx3_RJlnng3EmDYutmN4ul-Z1t2FL7uio_7Q-8QH6T3fZnpW7Ot9DwgkHU-PkW4P67ZsSmQeljUAlQPzgfQDfKYGO4R-ljHg-oiM1ZQTMYpCRcFAiTCEmGObriNGuFlG99DFx2oQ7U255k-xM1CAKJ54CFUIT6kZuZqvm5k-M-trE9t5gNjg5AsQR6KWZFrRq2KE1uah7JRIowkbBEMIfA2Uy7PlHP9xxnQMwIajE04MXNLWSzm_s6KFQBOvplz","summary":[{"type":"summary_text","text":"**Assessing pipe disconnect semantics and concurrency**"},{"type":"summary_text","text":"**Planning detailed Windows stream regression tests**"}]},{"id":"rs_0ceecce325b7fe2d016a5975901f3c8198aacbb5dd13bc5948","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXWV8Io1JitsIzhCG2kPWuG4sSEy_JvcKyY-8VwtQP_YPSmt0TkziOA-Lja9zKtqNvgoKNi8eyo19rUkwUMQKfaBFD2vhlLY7SnQ3UA6Ut1_bJANEI0cuQbAkZrp_c8gIQ7mEtKRT3XrrpXkcvBOwk-UN2ki6XV8IliC3qIegKirjnL7pZU4mxpSE6JNWLi-270lx76Wp96k3V3BpFgM_UyHeEkNiNa4OunUaArf68xxc-F_uge3A4zLDOReAdXpJ29u-Nieelf14s_y6JOQBofhEu5z6NdxAF5DFTwSBXyXPyeV--zMzGUKwzpXg-5k79mjtx_41J-tT3IwvyeXEIB6_A1Wp5Lorx_ofiCd5Pa_zx6dAUPlyuXyhDzjxE5SqTfva6zTa-1Id_wtMDg2MaJBZ9DsE9rQ0MN9BG8cnFZAMR8nKT4Hi3cqxTgy3vMdP_J08cjJbrHHivX7wBlky5c3oHJ5Pw_MVLOloDeWYr-oOr-YPbqhPuu4mj0Cs5ftTtT_LWtiDVzMc9sA4vMlWgPdhmudsGOkTP5Hf8IxQ6jnDIQM_SQ5edKELLLPjdUlNkcSQfr-O7ZnJkjzvdj-NZ2E36bmlKeDByCSt0E5X0zrHbhRJHu6JwLvNgxI_eV6REWKBClV5-LmzN794oCYtyPj-hFsBFdH-Hws8W1PE9cRIexbxLbRhhNUuXzfUa-4_wulKn8JxwIw1_3RVdIHYGHo48ws_fd8z8X6SpbMvg9uXypTzzex6KLZerFFlqYy0McLl3OE94JNHeTAiM8bL2R2BfziQnD5ZFQPUCB2IkS5ApW8zlgUpa6JfeL2CNy8G2Y-lV6EMzB6Wf-y5s0R5DEHMBEez-jte43Zq8NugcOjPTjFKAXDFIchzNrNMdwi84_DyfQfeCxIXNHMNYIgqHV0BrASivjtiE_ByhMa9Fig42VAEwKHa9lIpA_SxqHhpEL8obxGSKhgrAUjB-uAVE_zUJgcItS6THQZT2zhR6qyxi6mHFDtbKM5dud1ls5N3Y5xUQE8J4fEYATZtdcj9ts1ToNTx2oZ2Q4s9ExaUsT-IlHgLkEKXODkaO-5Twowc6uoj0F3dFk9O_cym1VgqBprSY5k6IVYUSrmTE6Y0TWwvS-aiwb3N1WGuCJdzBMx-sRJvJXLaqTEMozqTCqQKq1FOaMVq9L8Xo_w3GCDFuZsXfKTxl9MdihTO3AggbEYhO226JHwOXk53wZGuVv5gdHJRqn_-e450DZgU5UJvjvFgy4cPZBnTgIuv-zTieUXws7XnNIuWSimcZ_k-H5ybv8lfLUBbMlFe4y7I1uCKaAXud76P-BySJf3rL71nCgfIBtqfE4Z679suQ2SEkcxczsS-Jdey4q_-6MqJyy5-fIRHwxZYHCwu494n_d-nQZwPe069uylna6XZFLXF8WDgsiPFrv_MBJIEDNT67KajjiNlPMcX-oYL3kMiwvoATBatgoHhgwM3HWVe9wXDGhvN_ULrWWsuHjsqwPAfcxkZcY5bc1GgDhzivQjSpykP-rmIbKZ7o4WXJh2uRNmlTARr72BTZ2qf877u6OIvYpGmrDGpad70-80sklEuX90QbrVnMsV48IHehNPV-p_SDgzsrt7vXF7ZgWnNExH4uruAA5JBK5awYXbn3vZ0Q02uAQ7lhZWch_Zbb5Jdg2GwQS03NtrkgTxnygrru-37jNsAiRd4Kw02D7HrWukz04OPStlrBKUmq8OoezaplKSY_iC1rIdAfytajIBejWHb94mP5HaCSw=","summary":[{"type":"summary_text","text":"**Investigating release commit discrepancies**"}]},{"id":"fc_0ceecce325b7fe2d016a597595064c819893e40837616514d8","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading release heading guard\",\"path\":\".github/workflows/release.yml\",\"selector\":\"135-170\"}","call_id":"call_p2XvYfkOnQ7LyfYr4mnjP0Kh","name":"read"},{"id":"fc_0ceecce325b7fe2d016a597595065c81988ef29b22bf2b3781","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading changelog incident note\",\"path\":\"docs/RELEASE-RUNBOOK.md\",\"selector\":\"40-62\"}","call_id":"call_wQu5qXGOIfPpYxrrsHAnztLY","name":"read"},{"id":"fc_0ceecce325b7fe2d016a59759506608198a59bc876f5f3685b","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading v34 ADR decision\",\"path\":\"docs/adr/0038-broker-stream-lifecycle-truth.md\",\"selector\":\"1-170\"}","call_id":"call_AGQBo6u828zvoWCoUVAI7D7G","name":"read"},{"id":"fc_0ceecce325b7fe2d016a59759506688198bd1eef40bf5da8c0","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading retry timing constants\",\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"selector\":\"78-230\"}","call_id":"call_adl3PxzwiXaWSTJdk0bJnkng","name":"read"}]},"duration":32271.204400002956,"ttft":1301.009700000286,"contextSnapshot":{"promptTokens":79531,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_p2XvYfkOnQ7LyfYr4mnjP0Kh|fc_0ceecce325b7fe2d016a597595064c819893e40837616514d8","toolName":"read","startedAt":"2026-07-17T00:21:41.416Z","args":{"path":".github/workflows/release.yml"},"intent":"Reading release heading guard"},"id":"b542ce2e","parentId":"14e2e424","timestamp":"2026-07-17T00:21:41.416Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_wQu5qXGOIfPpYxrrsHAnztLY|fc_0ceecce325b7fe2d016a597595065c81988ef29b22bf2b3781","toolName":"read","startedAt":"2026-07-17T00:21:41.416Z","args":{"path":"docs/RELEASE-RUNBOOK.md"},"intent":"Reading changelog incident note"},"id":"b88ed5dd","parentId":"b542ce2e","timestamp":"2026-07-17T00:21:41.416Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_AGQBo6u828zvoWCoUVAI7D7G|fc_0ceecce325b7fe2d016a59759506608198a59bc876f5f3685b","toolName":"read","startedAt":"2026-07-17T00:21:41.416Z","args":{"path":"docs/adr/0038-broker-stream-lifecycle-truth.md"},"intent":"Reading v34 ADR decision"},"id":"47be9171","parentId":"b88ed5dd","timestamp":"2026-07-17T00:21:41.416Z"}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_adl3PxzwiXaWSTJdk0bJnkng|fc_0ceecce325b7fe2d016a59759506688198bd1eef40bf5da8c0","toolName":"read","startedAt":"2026-07-17T00:21:41.416Z","args":{"path":"crates/spt-daemon/src/dispatch.rs"},"intent":"Reading retry timing constants"},"id":"6c1284e2","parentId":"47be9171","timestamp":"2026-07-17T00:21:41.416Z"}
{"type":"message","id":"0a6c7b32","parentId":"6c1284e2","timestamp":"2026-07-17T00:21:41.421Z","message":{"role":"toolResult","toolCallId":"call_wQu5qXGOIfPpYxrrsHAnztLY|fc_0ceecce325b7fe2d016a597595065c81988ef29b22bf2b3781","toolName":"read","content":[{"type":"text","text":"2. **Write the user-facing changelog** — add a `## [0.X.Y] - <date>` section at\n…\n   subsections. This section becomes the GitHub Release **body** verbatim\n   (`release.yml` extracts it via `--notes-file`), so it is the changelog every\n   spt user reads. Rules:\n   - **spt-user-facing UX only.** What a person running the `spt` CLI notices or\n     does differently. Name the actual commands/flags they type.\n   - **No internal lingo** — no requirement ids, crate names, commit hashes,\n     milestone/hazard codes, or under-the-hood mechanics. A reader who has never\n     seen the source must understand every line.\n   - **Flag breaking changes** prominently under Changed.\n   - The release **fails loudly** if the tagged version has no `## [0.X.Y]`\n     section — the changelog is not optional.\n   - **After inserting the new section, assert the version ladder is\n     contiguous**: `grep '^## \\[' CHANGELOG.md` and check the versions descend\n     with no gap. Inserting the new heading with an editor whose anchor spans the\n     previous heading can silently *consume* that heading (v0.34.0 did this to\n     `## [0.33.0]`), folding the prior release's body into the new section — and\n     since the body is published verbatim, the release notes then carry two\n     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\n3. **Tag**: `git tag v0.X.Y && git push origin v0.X.Y`. This triggers:\n   - `release.yml` — both runners build, the assemble job extracts this\n     version's `CHANGELOG.md` section into the release body and creates a\n     **draft** release on spt-releases with `spt-x86_64-linux`,\n     `spt-x86_64-windows.exe`, `SHA256SUMS`, `manifest.schema.json`,\n     `mock-adapter.zip`;\n   - `docs-publish.yml` — docs tree + repo-face sync to spt-releases.\n4. **Sign + publish** (local, one command):\n\n…\n\n\n[Showing lines 39-65 of 90. Use :66 to continue]"}],"details":{"truncation":{"content":"   subsections. This section becomes the GitHub Release **body** verbatim\r\n   (`release.yml` extracts it via `--notes-file`), so it is the changelog every\r\n   spt user reads. Rules:\r\n   - **spt-user-facing UX only.** What a person running the `spt` CLI notices or\r\n     does differently. Name the actual commands/flags they type.\r\n   - **No internal lingo** — no requirement ids, crate names, commit hashes,\r\n     milestone/hazard codes, or under-the-hood mechanics. A reader who has never\r\n     seen the source must understand every line.\r\n   - **Flag breaking changes** prominently under Changed.\r\n   - The release **fails loudly** if the tagged version has no `## [0.X.Y]`\r\n     section — the changelog is not optional.\r\n   - **After inserting the new section, assert the version ladder is\r\n     contiguous**: `grep '^## \\[' CHANGELOG.md` and check the versions descend\r\n     with no gap. Inserting the new heading with an editor whose anchor spans the\r\n     previous heading can silently *consume* that heading (v0.34.0 did this to\r\n     `## [0.33.0]`), folding the prior release's body into the new section — and\r\n     since the body is published verbatim, the release notes then carry two\r\n     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\r\n3. **Tag**: `git tag v0.X.Y && git push origin v0.X.Y`. This triggers:\r\n   - `release.yml` — both runners build, the assemble job extracts this\r\n     version's `CHANGELOG.md` section into the release body and creates a\r\n     **draft** release on spt-releases with `spt-x86_64-linux`,\r\n     `spt-x86_64-windows.exe`, `SHA256SUMS`, `manifest.schema.json`,\r\n     `mock-adapter.zip`;\r\n   - `docs-publish.yml` — docs tree + repo-face sync to spt-releases.\r\n4. **Sign + publish** (local, one command):\r\n\r","truncated":true,"truncatedBy":"lines","totalLines":52,"totalBytes":1786,"outputLines":27,"outputBytes":1786,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"2. **Write the user-facing changelog** — add a `## [0.X.Y] - <date>` section at\n…\n   subsections. This section becomes the GitHub Release **body** verbatim\r\n   (`release.yml` extracts it via `--notes-file`), so it is the changelog every\r\n   spt user reads. Rules:\r\n   - **spt-user-facing UX only.** What a person running the `spt` CLI notices or\r\n     does differently. Name the actual commands/flags they type.\r\n   - **No internal lingo** — no requirement ids, crate names, commit hashes,\r\n     milestone/hazard codes, or under-the-hood mechanics. A reader who has never\r\n     seen the source must understand every line.\r\n   - **Flag breaking changes** prominently under Changed.\r\n   - The release **fails loudly** if the tagged version has no `## [0.X.Y]`\r\n     section — the changelog is not optional.\r\n   - **After inserting the new section, assert the version ladder is\r\n     contiguous**: `grep '^## \\[' CHANGELOG.md` and check the versions descend\r\n     with no gap. Inserting the new heading with an editor whose anchor spans the\r\n     previous heading can silently *consume* that heading (v0.34.0 did this to\r\n     `## [0.33.0]`), folding the prior release's body into the new section — and\r\n     since the body is published verbatim, the release notes then carry two\r\n     releases. The grep is a two-second self-check; a candidate `xtask`/CI lint.\r\n3. **Tag**: `git tag v0.X.Y && git push origin v0.X.Y`. This triggers:\r\n   - `release.yml` — both runners build, the assemble job extracts this\r\n     version's `CHANGELOG.md` section into the release body and creates a\r\n     **draft** release on spt-releases with `spt-x86_64-linux`,\r\n     `spt-x86_64-windows.exe`, `SHA256SUMS`, `manifest.schema.json`,\r\n     `mock-adapter.zip`;\r\n   - `docs-publish.yml` — docs tree + repo-face sync to spt-releases.\r\n4. **Sign + publish** (local, one command):\r\n\r\n…\n","startLine":37,"lineNumbers":[37,null,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,null,79]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\RELEASE-RUNBOOK.md"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":90,"totalBytes":1786,"outputLines":27,"outputBytes":1786,"shownRange":{"start":39,"end":65},"nextOffset":66}}},"isError":false,"timestamp":1784247701420}}
{"type":"message","id":"02ec4b37","parentId":"0a6c7b32","timestamp":"2026-07-17T00:21:41.422Z","message":{"role":"toolResult","toolCallId":"call_p2XvYfkOnQ7LyfYr4mnjP0Kh|fc_0ceecce325b7fe2d016a597595064c819893e40837616514d8","toolName":"read","content":[{"type":"text","text":"      - name: Build docs bundle\n        # The platform-independent docs asset (ADR-0036 §4): built book +\n        # llms exports + raw .md + schema, one tar.gz. Signed into the\n        # update set by the maintainer's release-publish half.\n        # [impl->REQ-DOCS-RELEASE-ASSET]\n        run: |\n          export PATH=\"$HOME/.cargo/bin:$PATH\"\n          cargo run -p xtask -- docs-bundle dist/spt-docs.tar.gz\n      - name: Build user-facing release notes from CHANGELOG\n        # The release BODY is the user-facing changelog: the `## [<version>]`\n        # section of CHANGELOG.md (Added / Changed / Fixed, UX items only — no\n        # internal lingo). Authoring that section before tagging is a required\n        # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the\n        # release loudly rather than shipping an empty/boilerplate body — the\n        # changelog is not optional.\n        run: |\n          ver=\"${GITHUB_REF_NAME#v}\"   # v0.1.2 -> 0.1.2\n          notes=\"dist/RELEASE_NOTES.md\"\n          # Literal string matching (index, not regex) so the section header is\n          # matched identically across awk dialects — no bracket-escaping traps.\n          awk -v want=\"## [${ver}]\" '\n            index($0, want) == 1 { grab=1; next }\n            grab && index($0, \"## [\") == 1 { exit }\n            grab { print }\n          ' CHANGELOG.md > \"$notes\"\n          if ! grep -q '[^[:space:]]' \"$notes\"; then\n            echo \"::error::CHANGELOG.md has no '## [$ver]' section — add the user-facing changelog for this release before tagging (see docs/RELEASE-RUNBOOK.md).\"\n            exit 1\n          fi\n          echo \"----- release body for ${GITHUB_REF_NAME} -----\"\n          cat \"$notes\"\n      - name: Create draft release on the private channel\n        # [impl->REQ-RELEASE-CHANNEL-PRIVATE] publish target = bs-releases\n        # (ADR-0036 §2); counter/signing/update-set format continue unchanged.\n        # [int->REQ-RELEASE-CHANNEL-PRIVATE] the v0.32.0 (counter 59) ceremony\n        # exercises this assemble/draft leg end-to-end against the private\n        # channel — the first release on BigscreenVR/spt-bs-releases (W2 ruling:\n        # the release ceremony is the integration evidence, no test rig).\n        env:\n          GH_TOKEN: ${{ secrets.RELEASES_TOKEN }}\n        run: |\n…\n            dist/spt-docs.tar.gz\n\n[Showing lines 134-173 of 184. Use :174 to continue]"}],"details":{"truncation":{"content":"        # The platform-independent docs asset (ADR-0036 §4): built book +\r\n        # llms exports + raw .md + schema, one tar.gz. Signed into the\r\n        # update set by the maintainer's release-publish half.\r\n        # [impl->REQ-DOCS-RELEASE-ASSET]\r\n        run: |\r\n          export PATH=\"$HOME/.cargo/bin:$PATH\"\r\n          cargo run -p xtask -- docs-bundle dist/spt-docs.tar.gz\r\n      - name: Build user-facing release notes from CHANGELOG\r\n        # The release BODY is the user-facing changelog: the `## [<version>]`\r\n        # section of CHANGELOG.md (Added / Changed / Fixed, UX items only — no\r\n        # internal lingo). Authoring that section before tagging is a required\r\n        # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the\r\n        # release loudly rather than shipping an empty/boilerplate body — the\r\n        # changelog is not optional.\r\n        run: |\r\n          ver=\"${GITHUB_REF_NAME#v}\"   # v0.1.2 -> 0.1.2\r\n          notes=\"dist/RELEASE_NOTES.md\"\r\n          # Literal string matching (index, not regex) so the section header is\r\n          # matched identically across awk dialects — no bracket-escaping traps.\r\n          awk -v want=\"## [${ver}]\" '\r\n            index($0, want) == 1 { grab=1; next }\r\n            grab && index($0, \"## [\") == 1 { exit }\r\n            grab { print }\r\n          ' CHANGELOG.md > \"$notes\"\r\n          if ! grep -q '[^[:space:]]' \"$notes\"; then\r\n            echo \"::error::CHANGELOG.md has no '## [$ver]' section — add the user-facing changelog for this release before tagging (see docs/RELEASE-RUNBOOK.md).\"\r\n            exit 1\r\n          fi\r\n          echo \"----- release body for ${GITHUB_REF_NAME} -----\"\r\n          cat \"$notes\"\r\n      - name: Create draft release on the private channel\r\n        # [impl->REQ-RELEASE-CHANNEL-PRIVATE] publish target = bs-releases\r\n        # (ADR-0036 §2); counter/signing/update-set format continue unchanged.\r\n        # [int->REQ-RELEASE-CHANNEL-PRIVATE] the v0.32.0 (counter 59) ceremony\r\n        # exercises this assemble/draft leg end-to-end against the private\r\n        # channel — the first release on BigscreenVR/spt-bs-releases (W2 ruling:\r\n        # the release ceremony is the integration evidence, no test rig).\r\n        env:\r\n          GH_TOKEN: ${{ secrets.RELEASES_TOKEN }}\r\n        run: |\r","truncated":true,"truncatedBy":"lines","totalLines":51,"totalBytes":2328,"outputLines":40,"outputBytes":2328,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"      - name: Build docs bundle\n        # The platform-independent docs asset (ADR-0036 §4): built book +\r\n        # llms exports + raw .md + schema, one tar.gz. Signed into the\r\n        # update set by the maintainer's release-publish half.\r\n        # [impl->REQ-DOCS-RELEASE-ASSET]\r\n        run: |\r\n          export PATH=\"$HOME/.cargo/bin:$PATH\"\r\n          cargo run -p xtask -- docs-bundle dist/spt-docs.tar.gz\r\n      - name: Build user-facing release notes from CHANGELOG\r\n        # The release BODY is the user-facing changelog: the `## [<version>]`\r\n        # section of CHANGELOG.md (Added / Changed / Fixed, UX items only — no\r\n        # internal lingo). Authoring that section before tagging is a required\r\n        # release step (docs/RELEASE-RUNBOOK.md). A missing section fails the\r\n        # release loudly rather than shipping an empty/boilerplate body — the\r\n        # changelog is not optional.\r\n        run: |\r\n          ver=\"${GITHUB_REF_NAME#v}\"   # v0.1.2 -> 0.1.2\r\n          notes=\"dist/RELEASE_NOTES.md\"\r\n          # Literal string matching (index, not regex) so the section header is\r\n          # matched identically across awk dialects — no bracket-escaping traps.\r\n          awk -v want=\"## [${ver}]\" '\r\n            index($0, want) == 1 { grab=1; next }\r\n            grab && index($0, \"## [\") == 1 { exit }\r\n            grab { print }\r\n          ' CHANGELOG.md > \"$notes\"\r\n          if ! grep -q '[^[:space:]]' \"$notes\"; then\r\n            echo \"::error::CHANGELOG.md has no '## [$ver]' section — add the user-facing changelog for this release before tagging (see docs/RELEASE-RUNBOOK.md).\"\r\n            exit 1\r\n          fi\r\n          echo \"----- release body for ${GITHUB_REF_NAME} -----\"\r\n          cat \"$notes\"\r\n      - name: Create draft release on the private channel\r\n        # [impl->REQ-RELEASE-CHANNEL-PRIVATE] publish target = bs-releases\r\n        # (ADR-0036 §2); counter/signing/update-set format continue unchanged.\r\n        # [int->REQ-RELEASE-CHANNEL-PRIVATE] the v0.32.0 (counter 59) ceremony\r\n        # exercises this assemble/draft leg end-to-end against the private\r\n        # channel — the first release on BigscreenVR/spt-bs-releases (W2 ruling:\r\n        # the release ceremony is the integration evidence, no test rig).\r\n        env:\r\n          GH_TOKEN: ${{ secrets.RELEASES_TOKEN }}\r\n        run: |\r\n…\n            dist/spt-docs.tar.gz","startLine":133,"lineNumbers":[133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,null,183]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.github\\workflows\\release.yml"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":184,"totalBytes":2328,"outputLines":40,"outputBytes":2328,"shownRange":{"start":134,"end":173},"nextOffset":174}}},"isError":false,"timestamp":1784247701422}}
{"type":"message","id":"57a5d1af","parentId":"02ec4b37","timestamp":"2026-07-17T00:21:41.422Z","message":{"role":"toolResult","toolCallId":"call_AGQBo6u828zvoWCoUVAI7D7G|fc_0ceecce325b7fe2d016a59759506608198a59bc876f5f3685b","toolName":"read","content":[{"type":"text","text":"# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)\n\n- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day)\n- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\n\n## Context\n\nThe broker's `NetShared.streams` table is append-only in practice: rows are\ninserted once (nethost.rs ~649) and `StreamLog::finish` only marks EOF —\nno removal or retirement path exists. Stream *classification* is derived at\nredispatch time by peeking ring seq 0, but the `StreamLog` data ring is\nbounded (4096 transport chunks) and evicts the protocol opener under\ntraffic. Dispatcher claims are a per-process one-shot `HashSet` insert\nperformed before worker spawn, never cleared or retried.\n\nAfter any brain cycle (`spt daemon refresh`, `spt update` apply — same\npath), the fresh `dispatch::run_dispatch_loop` therefore replays EVERY\nbroker-held peer stream, including finished historical attach streams. A\nreplayed historical Attach silently steals the live controller\n(same-identity `become_controller`, no `Displaced`) and its replayed EOF\nclears it — the operator sees a frozen remote PTY (field: 4/5 endpoints,\ntwice on 2026-07-16). A high-traffic active stream whose opener evicted\nclassifies Unknown/Failed and is permanently abandoned (claim burned\npre-spawn).\n\n## Decision\n<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\n\nStream rows carry explicit **lifecycle truth**, and redispatch consumes it:\n\n1. **Finished is terminal for redispatch.** A finished/terminal stream row\n   is retired from redispatch eligibility (removed from the enumeration the\n   dispatcher claims from, or excluded by lifecycle state). Only the\n   post-EOF state genuinely needed by other readers (presence, log reads)\n   survives. A fresh dispatcher never re-serves a terminal Attach.\n   *Gate round 2 ruling: retirement is the EXCLUDED-BY-LIFECYCLE arm —\n   eligibility-visibility only, never transport teardown. Removing the row\n   races the worker's fire-and-forget reply send and resets the\n   never-finished send half (requester sees torn-EOF instead of its reply);\n   physical teardown belongs to the connection-close sweep alone.*\n2. **Classification identity is restart-durable and ring-independent.**\n   Each broker-held inbound stream pins an immutable bounded opener/\n   classification fact (the complete first NDJSON record or its derived\n   family+cursor facts) OUTSIDE the evictable data ring, held until stream\n   close. Recovery classifies from that metadata, never from ring seq 0.\n3. **Claims are retryable, outcomes are classified.** The dispatcher\n   distinguishes active/retryable vs served vs finished. A transient\n   worker-setup failure releases/requeues the claim; terminal outcomes do\n   not hot-loop.\n4. **Live reconstruction semantics are unchanged.** The legitimate\n   same-operator successor re-take after a brain restart (controller\n   identity keyed on `by`) still silently re-takes; the discriminator for\n   replay suppression is stream lifecycle, never origin identity.\n\n## Consequences\n\n- Brain cycles (refresh/update) stop being a controller-integrity hazard:\n  historical streams can neither take nor clear a live controller, and\n  active streams survive opener eviction — verified by production-path\n  regressions (the prior e2e manually re-served a stream and bypassed\n  rediscovery entirely).\n- The stream table gains a retirement duty (bounded growth instead of\n  forever-discoverable rows) and a small per-stream metadata cost (one\n  pinned opener record per stream, ring untouched).\n- Rejected alternatives: clearing the whole stream table on brain restart\n  (destroys live streams' reconstruction facts along with the stale ones);\n  enlarging the data ring to preserve openers (unbounded memory for a\n  correctness fact); filtering replay by origin identity (breaks the\n  legitimate successor re-take, the exact silent-steal seam); a\n  per-generation claimed-set handoff (patches the symptom, keeps terminal\n  rows eligible forever).\n"}],"details":{"displayContent":{"text":"# ADR-0038: Broker stream-table lifecycle truth (redispatch eligibility)\r\n\r\n- **Status:** accepted (hertz RCA + doyle seam-verify 2026-07-16, dispatched same day)\r\n- **Context:** REDISPATCH-TRUTH milestone; RCA `.claude/reports/2026-07-16-redispatch-truth/hertz-refresh-rca.txt`\r\n\r\n## Context\r\n\r\nThe broker's `NetShared.streams` table is append-only in practice: rows are\r\ninserted once (nethost.rs ~649) and `StreamLog::finish` only marks EOF —\r\nno removal or retirement path exists. Stream *classification* is derived at\r\nredispatch time by peeking ring seq 0, but the `StreamLog` data ring is\r\nbounded (4096 transport chunks) and evicts the protocol opener under\r\ntraffic. Dispatcher claims are a per-process one-shot `HashSet` insert\r\nperformed before worker spawn, never cleared or retried.\r\n\r\nAfter any brain cycle (`spt daemon refresh`, `spt update` apply — same\r\npath), the fresh `dispatch::run_dispatch_loop` therefore replays EVERY\r\nbroker-held peer stream, including finished historical attach streams. A\r\nreplayed historical Attach silently steals the live controller\r\n(same-identity `become_controller`, no `Displaced`) and its replayed EOF\r\nclears it — the operator sees a frozen remote PTY (field: 4/5 endpoints,\r\ntwice on 2026-07-16). A high-traffic active stream whose opener evicted\r\nclassifies Unknown/Failed and is permanently abandoned (claim burned\r\npre-spawn).\r\n\r\n## Decision\r\n<!-- [doc->REQ-REDISPATCH-FINISHED-RETIRE] -->\r\n\r\nStream rows carry explicit **lifecycle truth**, and redispatch consumes it:\r\n\r\n1. **Finished is terminal for redispatch.** A finished/terminal stream row\r\n   is retired from redispatch eligibility (removed from the enumeration the\r\n   dispatcher claims from, or excluded by lifecycle state). Only the\r\n   post-EOF state genuinely needed by other readers (presence, log reads)\r\n   survives. A fresh dispatcher never re-serves a terminal Attach.\r\n   *Gate round 2 ruling: retirement is the EXCLUDED-BY-LIFECYCLE arm —\r\n   eligibility-visibility only, never transport teardown. Removing the row\r\n   races the worker's fire-and-forget reply send and resets the\r\n   never-finished send half (requester sees torn-EOF instead of its reply);\r\n   physical teardown belongs to the connection-close sweep alone.*\r\n2. **Classification identity is restart-durable and ring-independent.**\r\n   Each broker-held inbound stream pins an immutable bounded opener/\r\n   classification fact (the complete first NDJSON record or its derived\r\n   family+cursor facts) OUTSIDE the evictable data ring, held until stream\r\n   close. Recovery classifies from that metadata, never from ring seq 0.\r\n3. **Claims are retryable, outcomes are classified.** The dispatcher\r\n   distinguishes active/retryable vs served vs finished. A transient\r\n   worker-setup failure releases/requeues the claim; terminal outcomes do\r\n   not hot-loop.\r\n4. **Live reconstruction semantics are unchanged.** The legitimate\r\n   same-operator successor re-take after a brain restart (controller\r\n   identity keyed on `by`) still silently re-takes; the discriminator for\r\n   replay suppression is stream lifecycle, never origin identity.\r\n\r\n## Consequences\r\n\r\n- Brain cycles (refresh/update) stop being a controller-integrity hazard:\r\n  historical streams can neither take nor clear a live controller, and\r\n  active streams survive opener eviction — verified by production-path\r\n  regressions (the prior e2e manually re-served a stream and bypassed\r\n  rediscovery entirely).\r\n- The stream table gains a retirement duty (bounded growth instead of\r\n  forever-discoverable rows) and a small per-stream metadata cost (one\r\n  pinned opener record per stream, ring untouched).\r\n- Rejected alternatives: clearing the whole stream table on brain restart\r\n  (destroys live streams' reconstruction facts along with the stale ones);\r\n  enlarging the data ring to preserve openers (unbounded memory for a\r\n  correctness fact); filtering replay by origin identity (breaks the\r\n  legitimate successor re-take, the exact silent-steal seam); a\r\n  per-generation claimed-set handoff (patches the symptom, keeps terminal\r\n  rows eligible forever).\r\n","startLine":1,"lineNumbers":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\docs\\adr\\0038-broker-stream-lifecycle-truth.md"}}},"isError":false,"timestamp":1784247701422}}
{"type":"message","id":"149b7ab3","parentId":"57a5d1af","timestamp":"2026-07-17T00:21:41.430Z","message":{"role":"toolResult","toolCallId":"call_adl3PxzwiXaWSTJdk0bJnkng|fc_0ceecce325b7fe2d016a59759506688198bd1eef40bf5da8c0","toolName":"read","content":[{"type":"text","text":"use crate::notifsync::{apply_notif_feed, NotifPolicy};\nuse crate::propagate::serve_update;\nuse crate::registryhost::{RegistryGatePolicy, RegistryHost};\nuse crate::relcache::ReleaseCache;\nuse crate::sync::{serve_sync, SyncPolicy};\nuse crate::wan::receive_wan;\nuse crate::xfer::serve_xfer;\n\n/// Poll cadence for the stream-table watch (the dispatcher's only busy-wait;\n/// workers block on their stream's event feed).\npub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\n\n/// What protocol a peer-initiated stream speaks, by its first record's shape.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum StreamFamily {\n    /// A context sync pull (`SyncRecord::Request`).\n    Sync,\n    /// An update pull (`UpdRecord::Query`).\n    Update,\n    /// A notif replication feed (`NotifRecord::Row`).\n    Notif,\n    /// A WAN message feed (untagged `WanMessage` lines).\n    WanMsg,\n    /// A registry replication feed (untagged `RegistryUpdate` lines).\n    Registry,\n    /// A remote-drive attach (`AttachRecord::Request`).\n    Attach,\n    /// A file transfer (`XferRecord::Fetch`/`Push`).\n    Xfer,\n    /// A remote rest op (`RestRecord::Request` — suspend/wake from another\n    /// node, M5-D5b).\n    Rest,\n    /// A cross-node shell-link op (`ShellLinkRecord::Request` — relink/cmd\n    /// against an owner's shell on this node, M5-D8c).\n    ShellLink,\n    /// A subnet-scoped liveness probe (`ServeProbeRecord::Request` — \"serving\n    /// subnet X?\", the `--nodes` view's detach-aware refinement, REQ-SUBNET-5).\n    ServeProbe,\n    /// Unclassifiable — dropped.\n    Unknown,\n}\n\n/// Classify one decoded first line by shape (module-docs demux table).\npub fn classify_first_line(line: &serde_json::Value) -> StreamFamily {\n    match line.get(\"kind\").and_then(|k| k.as_str()) {\n        Some(\"request\") if line.get(\"sync_id\").is_some() => StreamFamily::Sync,\n        Some(\"request\") if line.get(\"session_id\").is_some() => StreamFamily::Attach,\n        Some(\"request\") if line.get(\"endpoint\").is_some() && line.get(\"event\").is_some() => {\n            StreamFamily::Rest\n        }\n        Some(\"request\") if line.get(\"owner\").is_some() && line.get(\"shell_ref\").is_some() => {\n            StreamFamily::ShellLink\n        }\n        Some(\"request\") if line.get(\"serve_probe\").is_some() => StreamFamily::ServeProbe,\n        Some(\"query\") => StreamFamily::Update,\n        // The status-only convergence query rides the update family (M8\n        // decision 19, REQ-UPD-6) — same serve loop, no artifact bytes.\n        Some(\"status_query\") => StreamFamily::Update,\n        Some(\"row\") => StreamFamily::Notif,\n        Some(\"fetch\") | Some(\"push\") => StreamFamily::Xfer,\n        Some(_) => StreamFamily::Unknown,\n        None => {\n            if line.get(\"target\").is_some() && line.get(\"op_id\").is_some() {\n                StreamFamily::WanMsg\n            } else if line.get(\"subnet\").is_some() && line.get(\"instance\").is_some() {\n                StreamFamily::Registry\n            } else if line.get(\"subnet\").is_some()\n                && line.get(\"node\").is_some()\n                && line.get(\"label\").is_some()\n            {\n                // A node-level label feed (REQ-SUBNET-3): an endpoint-less peer\n                // sends label records first (no instance rows), so the feed's\n                // first line is a NodeLabelUpdate. Same serve arm, same gate.\n                StreamFamily::Registry\n            } else {\n                StreamFamily::Unknown\n            }\n        }\n    }\n}\n\n/// Why a dispatched stream's worker ended — loud + typed, never silent.\n#[derive(Debug)]\npub enum DispatchOutcome {\n    /// The family's serve loop ran to its own outcome (rendered for the log).\n    Served(String),\n    /// The first line never arrived / didn't parse — dropped.\n    Unknown,\n    /// The worker failed (IO against broker or store).\n    Failed(String),\n}\n\n/// A claimed stream's dispatch lifecycle (ADR-0038 C,\n/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n/// insert performed BEFORE worker spawn and never cleared — one transient\n/// worker-setup failure permanently abandoned the stream. Claims now\n/// distinguish in-flight vs terminal vs retryable, and a transient failure\n/// requeues bounded (backoff-shaped, never a hot loop).\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum ClaimState {\n    /// A worker owns the stream right now (`attempts` = prior failures).\n    InFlight { attempts: u32 },\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\n    Terminal,\n    /// A transient failure released the claim; eligible again at `next_at`.\n    Retry { attempts: u32, next_at: Instant },\n}\n\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n/// failures recover, a persistently-failing stream goes terminal loud.\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\n\n/// Backoff base for a released claim (doubles per failed attempt).\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\n\n/// The backoff before retry number `attempts` (1-based) runs.\nfn retry_backoff(attempts: u32) -> Duration {\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\n}\n\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\n/// off). The unit-tested seam for the no-hot-loop ruling.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\n    match state {\n        None => Some(0),\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\n        _ => None,\n    }\n}\n\n/// PURE outcome transition: what a finished worker's claim becomes. Served\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\n/// hot-loops. A failure requeues with backoff until the bounded budget is\n/// spent, then goes terminal.\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\nfn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\n    match outcome {\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\n        DispatchOutcome::Failed(_) => {\n            let attempts = attempts + 1;\n            if attempts >= MAX_DISPATCH_ATTEMPTS {\n                ClaimState::Terminal\n            } else {\n                ClaimState::Retry {\n                    attempts,\n                    next_at: now + retry_backoff(attempts),\n                }\n            }\n        }\n    }\n}\n\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\n\n[Showing lines 77-233 of 1034. Use :234 to continue]"}],"details":{"truncation":{"content":"use crate::notifsync::{apply_notif_feed, NotifPolicy};\r\nuse crate::propagate::serve_update;\r\nuse crate::registryhost::{RegistryGatePolicy, RegistryHost};\r\nuse crate::relcache::ReleaseCache;\r\nuse crate::sync::{serve_sync, SyncPolicy};\r\nuse crate::wan::receive_wan;\r\nuse crate::xfer::serve_xfer;\r\n\r\n/// Poll cadence for the stream-table watch (the dispatcher's only busy-wait;\r\n/// workers block on their stream's event feed).\r\npub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\r\n\r\n/// What protocol a peer-initiated stream speaks, by its first record's shape.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub enum StreamFamily {\r\n    /// A context sync pull (`SyncRecord::Request`).\r\n    Sync,\r\n    /// An update pull (`UpdRecord::Query`).\r\n    Update,\r\n    /// A notif replication feed (`NotifRecord::Row`).\r\n    Notif,\r\n    /// A WAN message feed (untagged `WanMessage` lines).\r\n    WanMsg,\r\n    /// A registry replication feed (untagged `RegistryUpdate` lines).\r\n    Registry,\r\n    /// A remote-drive attach (`AttachRecord::Request`).\r\n    Attach,\r\n    /// A file transfer (`XferRecord::Fetch`/`Push`).\r\n    Xfer,\r\n    /// A remote rest op (`RestRecord::Request` — suspend/wake from another\r\n    /// node, M5-D5b).\r\n    Rest,\r\n    /// A cross-node shell-link op (`ShellLinkRecord::Request` — relink/cmd\r\n    /// against an owner's shell on this node, M5-D8c).\r\n    ShellLink,\r\n    /// A subnet-scoped liveness probe (`ServeProbeRecord::Request` — \"serving\r\n    /// subnet X?\", the `--nodes` view's detach-aware refinement, REQ-SUBNET-5).\r\n    ServeProbe,\r\n    /// Unclassifiable — dropped.\r\n    Unknown,\r\n}\r\n\r\n/// Classify one decoded first line by shape (module-docs demux table).\r\npub fn classify_first_line(line: &serde_json::Value) -> StreamFamily {\r\n    match line.get(\"kind\").and_then(|k| k.as_str()) {\r\n        Some(\"request\") if line.get(\"sync_id\").is_some() => StreamFamily::Sync,\r\n        Some(\"request\") if line.get(\"session_id\").is_some() => StreamFamily::Attach,\r\n        Some(\"request\") if line.get(\"endpoint\").is_some() && line.get(\"event\").is_some() => {\r\n            StreamFamily::Rest\r\n        }\r\n        Some(\"request\") if line.get(\"owner\").is_some() && line.get(\"shell_ref\").is_some() => {\r\n            StreamFamily::ShellLink\r\n        }\r\n        Some(\"request\") if line.get(\"serve_probe\").is_some() => StreamFamily::ServeProbe,\r\n        Some(\"query\") => StreamFamily::Update,\r\n        // The status-only convergence query rides the update family (M8\r\n        // decision 19, REQ-UPD-6) — same serve loop, no artifact bytes.\r\n        Some(\"status_query\") => StreamFamily::Update,\r\n        Some(\"row\") => StreamFamily::Notif,\r\n        Some(\"fetch\") | Some(\"push\") => StreamFamily::Xfer,\r\n        Some(_) => StreamFamily::Unknown,\r\n        None => {\r\n            if line.get(\"target\").is_some() && line.get(\"op_id\").is_some() {\r\n                StreamFamily::WanMsg\r\n            } else if line.get(\"subnet\").is_some() && line.get(\"instance\").is_some() {\r\n                StreamFamily::Registry\r\n            } else if line.get(\"subnet\").is_some()\r\n                && line.get(\"node\").is_some()\r\n                && line.get(\"label\").is_some()\r\n            {\r\n                // A node-level label feed (REQ-SUBNET-3): an endpoint-less peer\r\n                // sends label records first (no instance rows), so the feed's\r\n                // first line is a NodeLabelUpdate. Same serve arm, same gate.\r\n                StreamFamily::Registry\r\n            } else {\r\n                StreamFamily::Unknown\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// Why a dispatched stream's worker ended — loud + typed, never silent.\r\n#[derive(Debug)]\r\npub enum DispatchOutcome {\r\n    /// The family's serve loop ran to its own outcome (rendered for the log).\r\n    Served(String),\r\n    /// The first line never arrived / didn't parse — dropped.\r\n    Unknown,\r\n    /// The worker failed (IO against broker or store).\r\n    Failed(String),\r\n}\r\n\r\n/// A claimed stream's dispatch lifecycle (ADR-0038 C,\r\n/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\r\n/// insert performed BEFORE worker spawn and never cleared — one transient\r\n/// worker-setup failure permanently abandoned the stream. Claims now\r\n/// distinguish in-flight vs terminal vs retryable, and a transient failure\r\n/// requeues bounded (backoff-shaped, never a hot loop).\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum ClaimState {\r\n    /// A worker owns the stream right now (`attempts` = prior failures).\r\n    InFlight { attempts: u32 },\r\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\r\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\r\n    Terminal,\r\n    /// A transient failure released the claim; eligible again at `next_at`.\r\n    Retry { attempts: u32, next_at: Instant },\r\n}\r\n\r\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\r\n/// failures recover, a persistently-failing stream goes terminal loud.\r\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\r\n\r\n/// Backoff base for a released claim (doubles per failed attempt).\r\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\r\n\r\n/// The backoff before retry number `attempts` (1-based) runs.\r\nfn retry_backoff(attempts: u32) -> Duration {\r\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\r\n}\r\n\r\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\r\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\r\n/// off). The unit-tested seam for the no-hot-loop ruling.\r\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\r\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\r\n    match state {\r\n        None => Some(0),\r\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\r\n        _ => None,\r\n    }\r\n}\r\n\r\n/// PURE outcome transition: what a finished worker's claim becomes. Served\r\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\r\n/// hot-loops. A failure requeues with backoff until the bounded budget is\r\n/// spent, then goes terminal.\r\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\r\nfn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\r\n    match outcome {\r\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\r\n        DispatchOutcome::Failed(_) => {\r\n            let attempts = attempts + 1;\r\n            if attempts >= MAX_DISPATCH_ATTEMPTS {\r\n                ClaimState::Terminal\r\n            } else {\r\n                ClaimState::Retry {\r\n                    attempts,\r\n                    next_at: now + retry_backoff(attempts),\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\r\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\r","truncated":true,"truncatedBy":"lines","totalLines":958,"totalBytes":6920,"outputLines":157,"outputBytes":6920,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"use crate::notifsync::{apply_notif_feed, NotifPolicy};\r\nuse crate::propagate::serve_update;\r\nuse crate::registryhost::{RegistryGatePolicy, RegistryHost};\r\nuse crate::relcache::ReleaseCache;\r\nuse crate::sync::{serve_sync, SyncPolicy};\r\nuse crate::wan::receive_wan;\r\nuse crate::xfer::serve_xfer;\r\n\r\n/// Poll cadence for the stream-table watch (the dispatcher's only busy-wait;\r\n/// workers block on their stream's event feed).\r\npub const DEFAULT_DISPATCH_POLL: Duration = Duration::from_millis(100);\r\n\r\n/// What protocol a peer-initiated stream speaks, by its first record's shape.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\npub enum StreamFamily {\r\n    /// A context sync pull (`SyncRecord::Request`).\r\n    Sync,\r\n    /// An update pull (`UpdRecord::Query`).\r\n    Update,\r\n    /// A notif replication feed (`NotifRecord::Row`).\r\n    Notif,\r\n    /// A WAN message feed (untagged `WanMessage` lines).\r\n    WanMsg,\r\n    /// A registry replication feed (untagged `RegistryUpdate` lines).\r\n    Registry,\r\n    /// A remote-drive attach (`AttachRecord::Request`).\r\n    Attach,\r\n    /// A file transfer (`XferRecord::Fetch`/`Push`).\r\n    Xfer,\r\n    /// A remote rest op (`RestRecord::Request` — suspend/wake from another\r\n    /// node, M5-D5b).\r\n    Rest,\r\n    /// A cross-node shell-link op (`ShellLinkRecord::Request` — relink/cmd\r\n    /// against an owner's shell on this node, M5-D8c).\r\n    ShellLink,\r\n    /// A subnet-scoped liveness probe (`ServeProbeRecord::Request` — \"serving\r\n    /// subnet X?\", the `--nodes` view's detach-aware refinement, REQ-SUBNET-5).\r\n    ServeProbe,\r\n    /// Unclassifiable — dropped.\r\n    Unknown,\r\n}\r\n\r\n/// Classify one decoded first line by shape (module-docs demux table).\r\npub fn classify_first_line(line: &serde_json::Value) -> StreamFamily {\r\n    match line.get(\"kind\").and_then(|k| k.as_str()) {\r\n        Some(\"request\") if line.get(\"sync_id\").is_some() => StreamFamily::Sync,\r\n        Some(\"request\") if line.get(\"session_id\").is_some() => StreamFamily::Attach,\r\n        Some(\"request\") if line.get(\"endpoint\").is_some() && line.get(\"event\").is_some() => {\r\n            StreamFamily::Rest\r\n        }\r\n        Some(\"request\") if line.get(\"owner\").is_some() && line.get(\"shell_ref\").is_some() => {\r\n            StreamFamily::ShellLink\r\n        }\r\n        Some(\"request\") if line.get(\"serve_probe\").is_some() => StreamFamily::ServeProbe,\r\n        Some(\"query\") => StreamFamily::Update,\r\n        // The status-only convergence query rides the update family (M8\r\n        // decision 19, REQ-UPD-6) — same serve loop, no artifact bytes.\r\n        Some(\"status_query\") => StreamFamily::Update,\r\n        Some(\"row\") => StreamFamily::Notif,\r\n        Some(\"fetch\") | Some(\"push\") => StreamFamily::Xfer,\r\n        Some(_) => StreamFamily::Unknown,\r\n        None => {\r\n            if line.get(\"target\").is_some() && line.get(\"op_id\").is_some() {\r\n                StreamFamily::WanMsg\r\n            } else if line.get(\"subnet\").is_some() && line.get(\"instance\").is_some() {\r\n                StreamFamily::Registry\r\n            } else if line.get(\"subnet\").is_some()\r\n                && line.get(\"node\").is_some()\r\n                && line.get(\"label\").is_some()\r\n            {\r\n                // A node-level label feed (REQ-SUBNET-3): an endpoint-less peer\r\n                // sends label records first (no instance rows), so the feed's\r\n                // first line is a NodeLabelUpdate. Same serve arm, same gate.\r\n                StreamFamily::Registry\r\n            } else {\r\n                StreamFamily::Unknown\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// Why a dispatched stream's worker ended — loud + typed, never silent.\r\n#[derive(Debug)]\r\npub enum DispatchOutcome {\r\n    /// The family's serve loop ran to its own outcome (rendered for the log).\r\n    Served(String),\r\n    /// The first line never arrived / didn't parse — dropped.\r\n    Unknown,\r\n    /// The worker failed (IO against broker or store).\r\n    Failed(String),\r\n}\r\n\r\n/// A claimed stream's dispatch lifecycle (ADR-0038 C,\r\n/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\r\n/// insert performed BEFORE worker spawn and never cleared — one transient\r\n/// worker-setup failure permanently abandoned the stream. Claims now\r\n/// distinguish in-flight vs terminal vs retryable, and a transient failure\r\n/// requeues bounded (backoff-shaped, never a hot loop).\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum ClaimState {\r\n    /// A worker owns the stream right now (`attempts` = prior failures).\r\n    InFlight { attempts: u32 },\r\n    /// Done for this dispatcher's lifetime: served, unclassifiable, or\r\n    /// retries exhausted. Terminal outcomes never re-enter the queue.\r\n    Terminal,\r\n    /// A transient failure released the claim; eligible again at `next_at`.\r\n    Retry { attempts: u32, next_at: Instant },\r\n}\r\n\r\n/// Bounded retry budget per stream per dispatcher lifetime — transient setup\r\n/// failures recover, a persistently-failing stream goes terminal loud.\r\nconst MAX_DISPATCH_ATTEMPTS: u32 = 3;\r\n\r\n/// Backoff base for a released claim (doubles per failed attempt).\r\nconst DISPATCH_RETRY_BASE: Duration = Duration::from_millis(500);\r\n\r\n/// The backoff before retry number `attempts` (1-based) runs.\r\nfn retry_backoff(attempts: u32) -> Duration {\r\n    DISPATCH_RETRY_BASE * 2u32.saturating_pow(attempts.saturating_sub(1))\r\n}\r\n\r\n/// PURE claim decision: may this row be (re)claimed now? `Some(prior\r\n/// failures)` = claim it; `None` = leave it (in flight, terminal, or backing\r\n/// off). The unit-tested seam for the no-hot-loop ruling.\r\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\r\nfn should_claim(state: Option<&ClaimState>, now: Instant) -> Option<u32> {\r\n    match state {\r\n        None => Some(0),\r\n        Some(ClaimState::Retry { attempts, next_at }) if now >= *next_at => Some(*attempts),\r\n        _ => None,\r\n    }\r\n}\r\n\r\n/// PURE outcome transition: what a finished worker's claim becomes. Served\r\n/// and Unknown (unclassifiable) are TERMINAL — a terminal classification never\r\n/// hot-loops. A failure requeues with backoff until the bounded budget is\r\n/// spent, then goes terminal.\r\n// [impl->REQ-DISPATCH-CLAIM-RETRY]\r\nfn outcome_transition(outcome: &DispatchOutcome, attempts: u32, now: Instant) -> ClaimState {\r\n    match outcome {\r\n        DispatchOutcome::Served(_) | DispatchOutcome::Unknown => ClaimState::Terminal,\r\n        DispatchOutcome::Failed(_) => {\r\n            let attempts = attempts + 1;\r\n            if attempts >= MAX_DISPATCH_ATTEMPTS {\r\n                ClaimState::Terminal\r\n            } else {\r\n                ClaimState::Retry {\r\n                    attempts,\r\n                    next_at: now + retry_backoff(attempts),\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// PURE lifecycle+family discriminator (gate round 1, ADR-0038 Decision 1's\r\n/// \"excluded by lifecycle state\" half): is a `finished` row TERMINAL for\r","startLine":77,"lineNumbers":[77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\dispatch.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":1034,"totalBytes":6920,"outputLines":157,"outputBytes":6920,"shownRange":{"start":77,"end":233},"nextOffset":234}}},"isError":false,"timestamp":1784247701430}}
{"type":"message","id":"92d85667","parentId":"149b7ab3","timestamp":"2026-07-17T00:21:46.095Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Searching registry IDs in requirements**"},{"type":"toolCall","id":"call_YsDcnQcy5t1asQh7QVCm41Zq|fc_0ceecce325b7fe2d016a59759896b08198a79cb21dd8a9d2ef","name":"grep","arguments":{"i":"Locating v34 requirements","pattern":"REQ-REDISPATCH-FINISHED-RETIRE|REQ-STREAM-OPENER-DURABLE|REQ-DISPATCH-CLAIM-RETRY|REQ-DAEMON-REFRESH","path":"traceable-reqs.toml","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":8310,"output":115,"cacheRead":76288,"cacheWrite":0,"totalTokens":84713,"reasoningTokens":21,"cost":{"input":0.041550000000000004,"output":0.00345,"cacheRead":0.038144,"cacheWrite":0,"total":0.083144}},"stopReason":"toolUse","timestamp":1784247701438,"responseId":"resp_0ceecce325b7fe2d016a597595e6688198967a45cc61fbd311","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a59759777208198b276cfb09682b4de","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXWYlO2F7KBtBDt7jRSSK9JMFIUmMOzoKcvaaZJV46P_m8jt1iVamZ0kfaky4yAA0KJcsYWgyeuF2EKFSWtwl4oy7SX0b0Xdl3wMD0hwK5c1eLrOiAad3fh8fPo7W8rUjzf9wJhoCsD_AQYz4MFtg5cjzprA0ljpd_QQWHFHVgLhZYPqaBDCsN3_3nlmCXm5eZgeQqd9WVm-Qm0JamLGT5k15v4kIh5WS_HBi6e21rkjOs12x3E4aJQK4jvgJNTCGUWH6m6gKTtsC-iKPPBSP6vlGLbs2SPI05_WDQzIG8Z6QjvzA3lwPntoa9kjDIGJ101RJLftQyytY6NL9m31WjdVUGKXP1uhIMUhx2WYAYDphMW6qdneBesRWcrqmj0c4CKPRYOtpubdbjL7BLMvuBUv1ZTaPMS3okWEpHvvwkVegFETv1UFMhTjbpCYNZMLc891H2qYmOci3qoQrp-2IxcQ26r0ony1zwMK1wfmBQfBUosTawrwlXSA4EZzBN11dO9Pe09gc5be-74LP4vMJYFvG_6NhImT67EsQhnIpocFuC6lLbdTqXeGVWf0x4K4JZ7-YrzoN-1u34_la9bWY7MO1tf5VqkiJYiBjyDxYEyBRcu9NOnk9hT7NyWUubIIIB3vW6H8IDZC4rRMNN_wdNYKamFYMPKO9eGw9SvWtaMG8EDr8MZMUnhfliLW094isRjIRUYL7scmbrqUhE8p68ViMCsSDvQQmIsBjgyRW6_sJT6O7Oiiw2nTVEmXbMKvNOJzw6laTzL9sOMOP0oCxrR-dMW81l8LGQudta5S8iN6iFh5NaHriVts5Hm_RRhXKbGxCPtKX4ZMKgk6UrroI4ZdWiSoMTg2iRHiFZwk5ZRiOnz-a0q4_gdUaE1u8OfyzTdOmahD437hJmXF0ogt40hqDfX-sEFPB-0BkpliQfNZ_GZErUJdPjcotkpdXri-RjJToPjIOJXs4QbQA1ZZPzoO4amuQ1J8NWnHLEdepmL7aokfFEURKoXru798ebfuzGJ55vWnvRLlO_gOYsk1qYOnGA==","summary":[{"type":"summary_text","text":"**Searching registry IDs in requirements**"}]},{"id":"fc_0ceecce325b7fe2d016a59759896b08198a79cb21dd8a9d2ef","type":"function_call","status":"completed","arguments":"{\"i\":\"Locating v34 requirements\",\"pattern\":\"REQ-REDISPATCH-FINISHED-RETIRE|REQ-STREAM-OPENER-DURABLE|REQ-DISPATCH-CLAIM-RETRY|REQ-DAEMON-REFRESH\",\"path\":\"traceable-reqs.toml\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_YsDcnQcy5t1asQh7QVCm41Zq","name":"grep"}]},"duration":4655.560699999332,"ttft":2025.026399999857,"contextSnapshot":{"promptTokens":84598,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_YsDcnQcy5t1asQh7QVCm41Zq|fc_0ceecce325b7fe2d016a59759896b08198a79cb21dd8a9d2ef","toolName":"grep","startedAt":"2026-07-17T00:21:46.095Z","args":{"path":"traceable-reqs.toml"},"intent":"Locating v34 requirements"},"id":"a09a0e48","parentId":"92d85667","timestamp":"2026-07-17T00:21:46.095Z"}
{"type":"message","id":"feae7aa0","parentId":"a09a0e48","timestamp":"2026-07-17T00:21:46.132Z","message":{"role":"toolResult","toolCallId":"call_YsDcnQcy5t1asQh7QVCm41Zq|fc_0ceecce325b7fe2d016a59759896b08198a79cb21dd8a9d2ef","toolName":"grep","content":[{"type":"text","text":" 2395|[[requirements]]\n*2396|id = \"REQ-DAEMON-REFRESH\"\n 2397|title = \"THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah d...\n 2398|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2399|\n...\n 2433|id = \"REQ-HAZARD-REDISPATCH-CONTROL-STEAL\"\n*2434|title = \"REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 — 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream — a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int — product...\n 2435|required_stages = [\"doc\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). doc = KNOWN-HAZARDS 7.41; int = redispatch.rs D1 (Served-retired historical attach) + D1b gate-round-1 (finished-UNRETIRED orderings: kill gen1 mid-serve THEN detach — pre-existing-backlog/brain-down-detach shape; gen2 skips + retires-on-sight, current controller unharmed across multi-second polls) — both production run_dispatch_loop via killable dispatch_fixture generations. HEAVY at birth, all three strings.\n 2436|\n 2437|[[requirements]]\n*2438|id = \"REQ-REDISPATCH-FINISHED-RETIRE\"\n*2439|title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bound...\n 2440|required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16; int ADDED gate round 1; RULING gate round 2: retirement is ELIGIBILITY-VISIBILITY, never teardown — the \"lifecycle-exclude\" arm of the title, remove is REJECTED: the worker's reply send is fire-and-forget, so remove-on-retire races the still-queued reply inside the broker and resets the never-finished send half = requester torn-EOF/NoReply, proven 30/150 vs 0/150 A/B on kitsubito; physical teardown b...\n 2441|\n 2442|[[requirements]]\n*2443|id = \"REQ-STREAM-OPENER-DURABLE\"\n*2444|title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix B): stream classification identity is RESTART-DURABLE and independent of the evictable data ring — today reconstruction classifies via peek_first_line from ring seq 0, but StreamLog is a bounded 4096-transport-chunk ring (DEFAULT_STREAM_RING_CHUNKS, nethost.rs:111), so a high-traffic ACTIVE stream's opener/Request record evicts and replay classifies Unknown/Failed = active stream permanently abandoned. Pin an immutable bounded opener/classification fact...\n 2445|required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = StreamLog OpenerCapture (pinned outside the ring, bounded hunt) + net-stream-opener IPC + dispatcher opener-first classification (ring-peek fallback for the N-1 broker window) + serve_attach durable-Request seeding + floor_seq subscribe; unit = opener_pins_first_line_across_full_ring_roll + opener_capture_gives_up_bounded_on_a_newline_less_stream; int = redispatch.rs D2 (ring rolled via the SPT_STREAM...\n 2446|\n 2447|[[requirements]]\n*2448|id = \"REQ-DISPATCH-CLAIM-RETRY\"\n*2449|title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED — today claimed.insert(stream_id) happens PRE-spawn (dispatch.rs:206/214) and is never cleared or retried on Unknown/Failed, so one transient worker-setup failure permanently abandons the stream. Distinguish active/retryable vs served vs finished: a transient worker-setup failure releases/requeues the claim (bounded/backoff-shaped); terminal classification outcomes stay terminal and do NOT ...\n 2450|required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = ClaimState lifecycle (InFlight/Terminal/Retry, should_claim + outcome_transition pure seams, MAX 3 attempts, doubling backoff); unit = transient_failure_requeues_bounded_and_terminal_never_reenters (the no-hot-loop distinction in test); int = redispatch.rs D3 (SPT_DISPATCH_INJECT_TRANSIENT_ONCE valve — one injected failure, retry recovers, injection PROVEN fired in the generation log). HEAVY at birth.\n 2451|\n 2452|# ── MESH-RECOVERY (doyle triage 2026-07-16; ADR-0039; hertz RCA .claude/reports/2026-07-10-hertz-session/02-mesh-isolation-rca.md; KNOWN-HAZARDS 7.42). Stages flip at W1 build start (traceable-per-wave-activation). ──\n\n[Some lines truncated to 512 chars]"}],"details":{"scopePath":"traceable-reqs.toml","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\traceable-reqs.toml","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":8,"fileCount":1,"files":["traceable-reqs.toml"],"fileMatches":[{"path":"traceable-reqs.toml","count":8}],"truncated":true,"displayContent":" 2395│[[requirements]]\n*2396│id = \"REQ-DAEMON-REFRESH\"\n 2397│title = \"THE-FORKENING W4 (operator add 2026-07-14): `spt daemon refresh` — restart the daemon BRAIN without a binary swap and WITHOUT touching the broker: exactly the apply_staged brain-cycle path (brain stop -> respawn -> readiness trial -> promote, incl. the trial-drain drive REQ-UPDATE-TRIAL-DRAIN-DRIVE and viewer-only resume REQ-BRAIN-RESUME-NO-CONTROL-STEAL) minus the swap. Recovery verb for wedged brain-held state (field motivator 2026-07-14: endpoint bringup broken on a live daemon + deployah d...\n 2398│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # FLIPPED in the W4 build commit (todlando 2026-07-15).\n 2399│\n     │...\n 2433│id = \"REQ-HAZARD-REDISPATCH-CONTROL-STEAL\"\n*2434│title = \"REDISPATCH-TRUTH W1 (KNOWN-HAZARDS 7.41, hertz field RCA 2026-07-16 — 4/5 endpoints frozen per brain cycle): a fresh dispatcher must NEVER re-serve a terminal stream — a replayed historical Attach must not steal (same-identity silent become_controller, no Displaced) or clear (replayed-EOF detach_session) a LIVE controller. The legitimate same-by successor re-take after a brain restart still silently re-takes: the discriminator is stream LIFECYCLE, never origin identity. Gate: int — product...\n 2435│required_stages = [\"doc\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). doc = KNOWN-HAZARDS 7.41; int = redispatch.rs D1 (Served-retired historical attach) + D1b gate-round-1 (finished-UNRETIRED orderings: kill gen1 mid-serve THEN detach — pre-existing-backlog/brain-down-detach shape; gen2 skips + retires-on-sight, current controller unharmed across multi-second polls) — both production run_dispatch_loop via killable dispatch_fixture generations. HEAVY at birth, all three strings.\n 2436│\n 2437│[[requirements]]\n*2438│id = \"REQ-REDISPATCH-FINISHED-RETIRE\"\n*2439│title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix A): finished/terminal stream rows are RETIRED from redispatch eligibility — NetShared.streams today has NO removal path (single insert nethost.rs ~649; StreamLog::finish only marks) so every dispatcher generation re-enumerates every historical stream forever. Retire terminal rows from the enumeration the dispatcher claims from (remove, or lifecycle-exclude), preserving only the post-EOF state genuinely needed by other readers (presence/log reads); bound...\n 2440│required_stages = [\"doc\", \"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16; int ADDED gate round 1; RULING gate round 2: retirement is ELIGIBILITY-VISIBILITY, never teardown — the \"lifecycle-exclude\" arm of the title, remove is REJECTED: the worker's reply send is fire-and-forget, so remove-on-retire races the still-queued reply inside the broker and resets the never-finished send half = requester torn-EOF/NoReply, proven 30/150 vs 0/150 A/B on kitsubito; physical teardown b...\n 2441│\n 2442│[[requirements]]\n*2443│id = \"REQ-STREAM-OPENER-DURABLE\"\n*2444│title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix B): stream classification identity is RESTART-DURABLE and independent of the evictable data ring — today reconstruction classifies via peek_first_line from ring seq 0, but StreamLog is a bounded 4096-transport-chunk ring (DEFAULT_STREAM_RING_CHUNKS, nethost.rs:111), so a high-traffic ACTIVE stream's opener/Request record evicts and replay classifies Unknown/Failed = active stream permanently abandoned. Pin an immutable bounded opener/classification fact...\n 2445│required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = StreamLog OpenerCapture (pinned outside the ring, bounded hunt) + net-stream-opener IPC + dispatcher opener-first classification (ring-peek fallback for the N-1 broker window) + serve_attach durable-Request seeding + floor_seq subscribe; unit = opener_pins_first_line_across_full_ring_roll + opener_capture_gives_up_bounded_on_a_newline_less_stream; int = redispatch.rs D2 (ring rolled via the SPT_STREAM...\n 2446│\n 2447│[[requirements]]\n*2448│id = \"REQ-DISPATCH-CLAIM-RETRY\"\n*2449│title = \"REDISPATCH-TRUTH W1 (ADR-0038, hertz fix C): dispatcher claims are RETRYABLE and outcomes are CLASSIFIED — today claimed.insert(stream_id) happens PRE-spawn (dispatch.rs:206/214) and is never cleared or retried on Unknown/Failed, so one transient worker-setup failure permanently abandons the stream. Distinguish active/retryable vs served vs finished: a transient worker-setup failure releases/requeues the claim (bounded/backoff-shaped); terminal classification outcomes stay terminal and do NOT ...\n 2450│required_stages = [\"impl\", \"unit\", \"int\"]  # ACTIVATED REDISPATCH-TRUTH W1 (todlando 2026-07-16). impl = ClaimState lifecycle (InFlight/Terminal/Retry, should_claim + outcome_transition pure seams, MAX 3 attempts, doubling backoff); unit = transient_failure_requeues_bounded_and_terminal_never_reenters (the no-hot-loop distinction in test); int = redispatch.rs D3 (SPT_DISPATCH_INJECT_TRANSIENT_ONCE valve — one injected failure, retry recovers, injection PROVEN fired in the generation log). HEAVY at birth.\n 2451│\n 2452│# ── MESH-RECOVERY (doyle triage 2026-07-16; ADR-0039; hertz RCA .claude/reports/2026-07-10-hertz-session/02-mesh-isolation-rca.md; KNOWN-HAZARDS 7.42). Stages flip at W1 build start (traceable-per-wave-activation). ──","linesTruncated":true,"meta":{"limits":{"columnTruncated":{"maxColumn":512}}}},"isError":false,"timestamp":1784247706131}}
{"type":"message","id":"b3950472","parentId":"feae7aa0","timestamp":"2026-07-17T00:21:59.125Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Clarifying --restart behavior mismatch**\n\n**Analyzing session reconnect during update --restart**"},{"type":"toolCall","id":"call_1HJ32fiXZvOt9BCnbBTmAF4p|fc_0ceecce325b7fe2d016a5975a6c47c819893b3c6c10da47cd9","name":"read","arguments":{"i":"Reading full-restart update semantics","path":"crates/spt/src/cli.rs","selector":"5038-5095"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4799,"output":293,"cacheRead":81408,"cacheWrite":0,"totalTokens":86500,"reasoningTokens":250,"cost":{"input":0.023995000000000002,"output":0.008790000000000001,"cacheRead":0.040704,"cacheWrite":0,"total":0.073489}},"stopReason":"toolUse","timestamp":1784247706247,"responseId":"resp_0ceecce325b7fe2d016a59759b8a6881989684d983b73ef522","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a59759f34e481988f2c78debe3afacc","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXWmnz-y9nG-na-9Dk_UNFtM5M5Mya8S07l7NjtdnTXYYZZcokmQNJ1hfvAIpDNRARLND_-zkDxTxnDrTc8wsaXmHl4eUe2HjzWwXefvGvnifDXZiRDQ3kpbic-w9KFR2KfeMzoE-CvhpcTjPoac5eOnu2uRWZ9G_9xHqjUUVwQfo4zBgtoJr4nqakgUfO17xFSrSQwqP9dBIc1E6r0Ru1MaTRxAzT_YbAH5yfhvJappv0fV5A2gyEh1HhnfW1IewvrWJKNGryqbFfsVbT8RSJdj8LFw4XVAoh-BBNEtsgKAFf1sIt3cne1-JKAmh0ZhxMoxmFTfyonTYB1JbflDxZjVOSQgJh0H4o4-Vw0_1ieng9mQoJjKJScmhS7nmqItxigVw6UN3y4_gB1-4tt4BBg8izWVQ3ySP6coDYWmBQeNcLf5Xt1ZO7sBy2j8hSm6NbAeDihNaRoeEuhgrxzg7cfWf0rC5vSVN00tYb2YUcfYitM9IkGgnJOzXqXmrn3kL7gDLM0GaSrpoUtEiqvr5Y_QtibzZxN3twaIIgnMmJkhpeRRWBU4byGReLRGnSiFgTqfsKy-OLPkX2_YdjjlxW0OkvHSNHK6ZhGK6KNzUug6AyVSiAsaagUgXw86RRELzwt8bR_29zeyYOb0N3J6y0ouozMQHteGd-dSPaSAQkJkevMMhr_PJ91y-K8dYXeK3wi1ltSjnZmd9R4BOoxTNTCFysePpCr9-8arXX2hS0-Ow7ygGpR-AaX14q8vBm-xIyJFBOC7Yx60bRQH7EPF72zlruYMARFYjsccEdMlNlOYuV0GkxNNfPhcBA6QlKdLMAXy0OU92KSi4K8UqWvXza3CeqpyXW1i0tkwPNo2-Pg6Bxw_5X2CnwFErGKBm96bl5Equ4iXVWT3QwyjXBXq2EtAB9e65cJot-0wY0ZxZ1H_ZE0xwcTxx4p6mvrrhv0jzEU2UjlP5pwB4lqsQGTyg-IW5YjZsWP5eBfq8Dpb76CHxdm2Z95M2_v9W1o_uXe1fXRJageLfcfiV3BxYn83nQIvu6UAtQvx3HyD_SVT_VQcEJws9jgPT0tmOXPtURsmUaVjMVofrsAesOkT-CJJlbKhgjmfNTkFdgZJPYT_6w_tN-Mgs7eocnDYey5itb_bgNWv50Ok26Bch0b8VL-bBONSy-QBUSoLcq-88NfZ4dtoOY_x-muKlv7IvYxVsm6gj-hIlUPKNu-O9MFEZyPw_XF1MG317N-vOJt0ylVUvkNn_olmq8RqWZo-Nw63CTwsSujnnbWIMvDUR6K3bnZ05jXhqQ-7mqrinb5MpkpCdK6it2H6l8r1B4ZBozRIlEGW9hMm7t8Elh8EJg_8BEgvphQCZKmcuU8M214UTqShNHym0IWaYxPhYirZvRYcv9LeA61l7GT1WAzsRkIb3W86T1dDn2jlQwYZ6G3gYw-4U1PPldyUrdF-7ulyiEKooRVKDsFLm_LLU3VIme5uKZmzUNEJYt4ox6NC4sESNXjAqGQo_hIyFIYjmuOoqjqxmfZVUSTQCJP6U1iIZK_dFvwTGTntUoInDqi9Y_bzP_CEgs8r_Vnucc4VQK384nHgm95If0C92twZZa8873EJnDvso6l9OvXVPNJ9ZWOI7h6OY8D5lsnecxUc4pDzAzEigszvr_HWJ8Juhd5A3PSi2Vmqxtva_kjIo5iS233e1gEkKDCw_RiiPk8L6KhOdwZxWr41IPo328pxwjq_DhM50MngYvk5Kktw_zDQ_xptxquO3L9SWmtuxC_mawk0pRXtRdWTWBhfg7a-pRQXK6I1Y1yIzbE2t7RXfnWUqaMofRNWjg5BgPlABjuZY2x6RL4mMPh8KUXiDLRt2MRjs3ZWe9aUPgcmvTESCYnham1ZZGHP_v17oi_KQg71Xp90tidbRkiaR1EMuW8AfZvTPY-DN3xF9yYiyUb1oHG_GQyjfXSb5goWN8dAbfvAjNb2mtz7MuVbteBQ0iHKwlumZ1IpEbgk7mT2G2v3P_tQi6pE6AhL9ULk9rnIKsxscV5Q59DlrWq7LmgVbDow7aHnyvbopFiRtBEZGLvQtTvSPl8zZR3GIuw4fr8lfmYpHLoeaI_ASHmDaS8cn-c5JpQmf1HuEi3YMl0p4YxJjJyaFB8L0tfAtyfCG7yxya3E8_g1-KlRISBgGsCLDRmGMvYjUdqRHcgUwwNz66BNSFPiz1Qdmy1b5786A_4V_cL9D4ibteU10gr66Iw0rt1mL-B5sIxIosnTRfLKfC7DfBIvhDfhK5NaYkuyPWkpJgPIAAXDXPzj2iroa7ptgkQPJ3MyneJ9lGgqkyVLZyOjQ5_YQ7mRgjuxcA58gC-D8Wn8Htfrt0O-u9rMbf7JkQtrWoQy2Fw6U1jx77exOHoLgaBb4CfSYzFCSUbTnMqsNBcxacT67fACa6uf4C8jF-_XIfyBRVhVCcXPcLiTDuUNvnzH1rxOkJfOSlc_3dJvpmbzHGIAnasZCLXhZoRRWtDnqzjeooA8kSrDUymsTA==","summary":[{"type":"summary_text","text":"**Clarifying --restart behavior mismatch**"},{"type":"summary_text","text":"**Analyzing session reconnect during update --restart**"}]},{"id":"fc_0ceecce325b7fe2d016a5975a6c47c819893b3c6c10da47cd9","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading full-restart update semantics\",\"path\":\"crates/spt/src/cli.rs\",\"selector\":\"5038-5095\"}","call_id":"call_1HJ32fiXZvOt9BCnbBTmAF4p","name":"read"}]},"duration":12875.958999998868,"ttft":4952.239600002766,"contextSnapshot":{"promptTokens":86207,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_1HJ32fiXZvOt9BCnbBTmAF4p|fc_0ceecce325b7fe2d016a5975a6c47c819893b3c6c10da47cd9","toolName":"read","startedAt":"2026-07-17T00:21:59.125Z","args":{"path":"crates/spt/src/cli.rs"},"intent":"Reading full-restart update semantics"},"id":"af75b3c3","parentId":"b3950472","timestamp":"2026-07-17T00:21:59.125Z"}
{"type":"message","id":"f9e4d19d","parentId":"af75b3c3","timestamp":"2026-07-17T00:21:59.278Z","message":{"role":"toolResult","toolCallId":"call_1HJ32fiXZvOt9BCnbBTmAF4p|fc_0ceecce325b7fe2d016a5975a6c47c819893b3c6c10da47cd9","toolName":"read","content":[{"type":"text","text":"fn cmd_update_apply(finish: bool) -> i32 {\n…\n    match outcome {\n…\n        Err(e) => {\n            eprintln!(\"UPDATE_APPLY_FAIL:{e}\");\n            1\n        }\n    }\n}\n\n/// One leg of the bare `spt update` composite (REQ-UPDATE-DEFAULT-COMPOSITE,\n/// REQ-UPDATE-RESTART-SAFE-SWAP).\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\nenum UpdateLeg {\n    /// `update fetch --apply` — stage + install the core in one step (the\n    /// brain-only cycle: the invoking process survives by construction).\n    FetchApply,\n    /// `update fetch` — stage only; the swap waits for the Finish leg.\n    Fetch,\n    /// `update adapters` — sweep every gh_release adapter.\n    Adapters,\n    /// `update apply --finish` — swap + full daemon restart. LETHAL: from an\n    /// spt-hosted session the invoker's PTY dies here, so this leg is always\n    /// planned LAST (everything else has completed by then).\n    Finish,\n}\n\n/// Plan the composite's leg sequence — pure, unit-gated. The invariants:\n/// core before adapters (core-first doctrine); `--core-only` drops the\n/// adapters leg; `--restart` replaces the in-place apply with stage-now\n/// (`Fetch`) + `Finish` LAST (lethal-leg-last, operator-ruled: the whole-daemon\n/// restart must be the final act so every other leg completes from ANY\n/// invoking context, including a hosted session whose PTY the finish kills).\n// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\n// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\nfn plan_update_legs(core_only: bool, restart: bool) -> Vec<UpdateLeg> {\n    let mut legs = Vec::new();\n    legs.push(if restart {\n        UpdateLeg::Fetch\n    } else {\n        UpdateLeg::FetchApply\n    });\n    if !core_only {\n        legs.push(UpdateLeg::Adapters);\n    }\n    if restart {\n        legs.push(UpdateLeg::Finish);\n    }\n    legs\n}\n\n/// Bare `spt update` — the composite front door (REQ-UPDATE-DEFAULT-COMPOSITE):\n/// core first (`fetch --apply`; an already-current core no-ops and the run\n/// becomes adapters-only), then `update adapters`. The invoker survives the\n/// core leg by construction — apply cycles the BRAIN only, the broker and every\n/// PTY keep running (applyhost::apply_staged) — so no re-run machinery exists.\n/// With `--restart` (REQ-UPDATE-RESTART-SAFE-SWAP) the plan is fetch → adapters\n/// → `apply --finish` LAST: the one-step safe full-cycle swap.\n///\n/// Failure policy: a CORE-leg failure (fetch/apply refused, gh missing…)\n/// ABORTS the remaining legs — updating adapters against a channel that just\n/// refused is compounding, and a `--restart` finish after a failed fetch would\n/// bounce the daemon for nothing. An ADAPTERS-leg failure is isolated (that is\n/// its contract): loud, nonzero at exit, but the composite proceeds — under\n/// `--restart` the finish still completes the core cycle. The accepted\n/// asymmetry (new adapters briefly on old-activated core if the finish itself\n\n[Showing lines 5037-5098 of 19062. Use :5099 to continue]"}],"details":{"truncation":{"content":"            eprintln!(\"UPDATE_APPLY_FAIL:{e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// One leg of the bare `spt update` composite (REQ-UPDATE-DEFAULT-COMPOSITE,\r\n/// REQ-UPDATE-RESTART-SAFE-SWAP).\r\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\r\nenum UpdateLeg {\r\n    /// `update fetch --apply` — stage + install the core in one step (the\r\n    /// brain-only cycle: the invoking process survives by construction).\r\n    FetchApply,\r\n    /// `update fetch` — stage only; the swap waits for the Finish leg.\r\n    Fetch,\r\n    /// `update adapters` — sweep every gh_release adapter.\r\n    Adapters,\r\n    /// `update apply --finish` — swap + full daemon restart. LETHAL: from an\r\n    /// spt-hosted session the invoker's PTY dies here, so this leg is always\r\n    /// planned LAST (everything else has completed by then).\r\n    Finish,\r\n}\r\n\r\n/// Plan the composite's leg sequence — pure, unit-gated. The invariants:\r\n/// core before adapters (core-first doctrine); `--core-only` drops the\r\n/// adapters leg; `--restart` replaces the in-place apply with stage-now\r\n/// (`Fetch`) + `Finish` LAST (lethal-leg-last, operator-ruled: the whole-daemon\r\n/// restart must be the final act so every other leg completes from ANY\r\n/// invoking context, including a hosted session whose PTY the finish kills).\r\n// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\r\n// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\r\nfn plan_update_legs(core_only: bool, restart: bool) -> Vec<UpdateLeg> {\r\n    let mut legs = Vec::new();\r\n    legs.push(if restart {\r\n        UpdateLeg::Fetch\r\n    } else {\r\n        UpdateLeg::FetchApply\r\n    });\r\n    if !core_only {\r\n        legs.push(UpdateLeg::Adapters);\r\n    }\r\n    if restart {\r\n        legs.push(UpdateLeg::Finish);\r\n    }\r\n    legs\r\n}\r\n\r\n/// Bare `spt update` — the composite front door (REQ-UPDATE-DEFAULT-COMPOSITE):\r\n/// core first (`fetch --apply`; an already-current core no-ops and the run\r\n/// becomes adapters-only), then `update adapters`. The invoker survives the\r\n/// core leg by construction — apply cycles the BRAIN only, the broker and every\r\n/// PTY keep running (applyhost::apply_staged) — so no re-run machinery exists.\r\n/// With `--restart` (REQ-UPDATE-RESTART-SAFE-SWAP) the plan is fetch → adapters\r\n/// → `apply --finish` LAST: the one-step safe full-cycle swap.\r\n///\r\n/// Failure policy: a CORE-leg failure (fetch/apply refused, gh missing…)\r\n/// ABORTS the remaining legs — updating adapters against a channel that just\r\n/// refused is compounding, and a `--restart` finish after a failed fetch would\r\n/// bounce the daemon for nothing. An ADAPTERS-leg failure is isolated (that is\r\n/// its contract): loud, nonzero at exit, but the composite proceeds — under\r\n/// `--restart` the finish still completes the core cycle. The accepted\r\n/// asymmetry (new adapters briefly on old-activated core if the finish itself\r","truncated":true,"truncatedBy":"lines","totalLines":14026,"totalBytes":2857,"outputLines":62,"outputBytes":2857,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn cmd_update_apply(finish: bool) -> i32 {\n…\n    match outcome {\n…\n        Err(e) => {\n            eprintln!(\"UPDATE_APPLY_FAIL:{e}\");\r\n            1\r\n        }\r\n    }\r\n}\r\n\r\n/// One leg of the bare `spt update` composite (REQ-UPDATE-DEFAULT-COMPOSITE,\r\n/// REQ-UPDATE-RESTART-SAFE-SWAP).\r\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\r\nenum UpdateLeg {\r\n    /// `update fetch --apply` — stage + install the core in one step (the\r\n    /// brain-only cycle: the invoking process survives by construction).\r\n    FetchApply,\r\n    /// `update fetch` — stage only; the swap waits for the Finish leg.\r\n    Fetch,\r\n    /// `update adapters` — sweep every gh_release adapter.\r\n    Adapters,\r\n    /// `update apply --finish` — swap + full daemon restart. LETHAL: from an\r\n    /// spt-hosted session the invoker's PTY dies here, so this leg is always\r\n    /// planned LAST (everything else has completed by then).\r\n    Finish,\r\n}\r\n\r\n/// Plan the composite's leg sequence — pure, unit-gated. The invariants:\r\n/// core before adapters (core-first doctrine); `--core-only` drops the\r\n/// adapters leg; `--restart` replaces the in-place apply with stage-now\r\n/// (`Fetch`) + `Finish` LAST (lethal-leg-last, operator-ruled: the whole-daemon\r\n/// restart must be the final act so every other leg completes from ANY\r\n/// invoking context, including a hosted session whose PTY the finish kills).\r\n// [impl->REQ-UPDATE-DEFAULT-COMPOSITE]\r\n// [impl->REQ-UPDATE-RESTART-SAFE-SWAP]\r\nfn plan_update_legs(core_only: bool, restart: bool) -> Vec<UpdateLeg> {\r\n    let mut legs = Vec::new();\r\n    legs.push(if restart {\r\n        UpdateLeg::Fetch\r\n    } else {\r\n        UpdateLeg::FetchApply\r\n    });\r\n    if !core_only {\r\n        legs.push(UpdateLeg::Adapters);\r\n    }\r\n    if restart {\r\n        legs.push(UpdateLeg::Finish);\r\n    }\r\n    legs\r\n}\r\n\r\n/// Bare `spt update` — the composite front door (REQ-UPDATE-DEFAULT-COMPOSITE):\r\n/// core first (`fetch --apply`; an already-current core no-ops and the run\r\n/// becomes adapters-only), then `update adapters`. The invoker survives the\r\n/// core leg by construction — apply cycles the BRAIN only, the broker and every\r\n/// PTY keep running (applyhost::apply_staged) — so no re-run machinery exists.\r\n/// With `--restart` (REQ-UPDATE-RESTART-SAFE-SWAP) the plan is fetch → adapters\r\n/// → `apply --finish` LAST: the one-step safe full-cycle swap.\r\n///\r\n/// Failure policy: a CORE-leg failure (fetch/apply refused, gh missing…)\r\n/// ABORTS the remaining legs — updating adapters against a channel that just\r\n/// refused is compounding, and a `--restart` finish after a failed fetch would\r\n/// bounce the daemon for nothing. An ADAPTERS-leg failure is isolated (that is\r\n/// its contract): loud, nonzero at exit, but the composite proceeds — under\r\n/// `--restart` the finish still completes the core cycle. The accepted\r\n/// asymmetry (new adapters briefly on old-activated core if the finish itself\r","startLine":4997,"lineNumbers":[4997,null,5025,null,5036,5037,5038,5039,5040,5041,5042,5043,5044,5045,5046,5047,5048,5049,5050,5051,5052,5053,5054,5055,5056,5057,5058,5059,5060,5061,5062,5063,5064,5065,5066,5067,5068,5069,5070,5071,5072,5073,5074,5075,5076,5077,5078,5079,5080,5081,5082,5083,5084,5085,5086,5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\cli.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":19062,"totalBytes":2857,"outputLines":62,"outputBytes":2857,"shownRange":{"start":5037,"end":5098},"nextOffset":5099}}},"isError":false,"timestamp":1784247719278}}
{"type":"message","id":"5bcb163c","parentId":"f9e4d19d","timestamp":"2026-07-17T00:22:03.499Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning RC reconnect at line 1560**"},{"type":"toolCall","id":"call_cUqvmVvZj0dMyyhAyDUP4h7O|fc_0ceecce325b7fe2d016a5975aaaf008198b74a3c857cd3be07","name":"read","arguments":{"i":"Reading RC reconnect timer","path":"crates/spt/src/rc.rs","selector":"1545-1665"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3792,"output":54,"cacheRead":83456,"cacheWrite":0,"totalTokens":87302,"reasoningTokens":13,"cost":{"input":0.01896,"output":0.0016200000000000001,"cacheRead":0.041728,"cacheWrite":0,"total":0.062308}},"stopReason":"toolUse","timestamp":1784247719330,"responseId":"resp_0ceecce325b7fe2d016a5975a783c881989a75882502a736aa","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5975a9faac8198906d290b57a7a188","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXWq-8DTp1u7sGW_w2ka56fx6ED0fon-uQXASpwaxj1dwPqh4sAGWQXEAnd8CWkvbNzonaIhjoIroU9sPylWMII1136uIYZEWzUZzWWnbspSALz9c1PrPFrq7A1gH4hjSbWN6HTxUX5Ax_dpJSGsakrzqIGb32Y7RMrcjVoDLaKt5GNLRH7wj-S_hE3sqqBNV3QlQuLCuSTB87Pf0B-gLpDVv1p2mOoL8y-R_sbiT5Jyjr0OZI1A5YgKiWuyF311ly6NoBj0Km27rMXKvEQ5IoJa_Or13omuZBaKdv8y1U1emDTddF50HXjIBQDjykAFWXp9HwJAqp6WBljoFnucn7r9yT5orx2lq9HoNhumg5hXch5D9rdfZybHo53KPevZefoxxSYhSlVKZRyVoNqcYgLQAn776UpsNr61jEzo3tw_S3FZVt1WiOq9Jx4oKBoewxlT9ZDSIt--gYiai_uSEoUXpB49HZHzzeiNmiFTSftSEfKXK0sWQhPjdsTiN2nDA8tesi7BDSYJbmkeIaU8X7Ev7-G9UIqs8_Jjlh5n58S3TrgWEA72adFrdnVICfC5Qt60csLqjy_jBlS9ks1DWNIsdJzj_EdnGqSxDuCGVIuJ9Sc6yPOSGXgKwb8Ia0c-a6FeUzwi_U_yrxFvG6MH46myUKJ7U4k7piuyYkd-cbGvOVSli7I_A7eOo007m_ru-tgKj0jzm8iIvFFfm4FbCmsdLd2WWGreJwrBa7LDR1F9bhgzVaKha-n9mAQu6kNuwBIAynCLjOmIYLMTf6js2YBLzU2f_McQVTI-aRl_rwDDF-0xQm1_9TMt22YCEZXPd0nY4S1biLF3_vYCFh-6TOGGMvyLMcGTFQF4g7H22S436SjYp3T7WYBHfihZucIUQM8XbkZks4lPoPjNqLy32ejUObz9dEt3PsD2dYrvJoZG9qb72REZqa3_UP5w9grEf_DypmZGQOQklFligMTC1SAqZQ==","summary":[{"type":"summary_text","text":"**Planning RC reconnect at line 1560**"}]},{"id":"fc_0ceecce325b7fe2d016a5975aaaf008198b74a3c857cd3be07","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading RC reconnect timer\",\"path\":\"crates/spt/src/rc.rs\",\"selector\":\"1545-1665\"}","call_id":"call_cUqvmVvZj0dMyyhAyDUP4h7O","name":"read"}]},"duration":4167.655899994075,"ttft":2640.283499993384,"contextSnapshot":{"promptTokens":87248,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_cUqvmVvZj0dMyyhAyDUP4h7O|fc_0ceecce325b7fe2d016a5975aaaf008198b74a3c857cd3be07","toolName":"read","startedAt":"2026-07-17T00:22:03.499Z","args":{"path":"crates/spt/src/rc.rs"},"intent":"Reading RC reconnect timer"},"id":"b5cfd261","parentId":"5bcb163c","timestamp":"2026-07-17T00:22:03.499Z"}
{"type":"message","id":"7a31e4ea","parentId":"b5cfd261","timestamp":"2026-07-17T00:22:03.525Z","message":{"role":"toolResult","toolCallId":"call_cUqvmVvZj0dMyyhAyDUP4h7O|fc_0ceecce325b7fe2d016a5975aaaf008198b74a3c857cd3be07","toolName":"read","content":[{"type":"text","text":"fn attach_viewport(\n…\n) -> Result<PumpEnd, String> {\n    loop {\n…\n        if !view {\n            if let Ok((cols, rows)) = crossterm::terminal::size() {\n…\n            }\n        }\n\n        let mut decoder = AttachDecoder::new();\n        let end = pump(\n            &mut est.brain,\n            est.stream_id,\n            &est.ops,\n            rx,\n            &mut decoder,\n            out,\n            !view, // controller drives resize\n            mouse_mode,\n            status.as_deref_mut(),\n        );\n\n        // Final ends (incl. hard errors) exit the loop to the parting copy.\n        let severed_kind = match &end {\n            Ok(e) if reconnect_class(e) == EndClass::Reconnect => match e {\n                PumpEnd::BrokerGone => \"the daemon connection dropped\",\n                _ => \"the connection was severed\",\n            },\n            _ => return end,\n        };\n\n        // Reconnect: full-screen centered banner (operator language — the internal\n        // detail stays off the terminal per the Q4 UX rule), then re-drive\n        // establish_attach inside the bounded window. A detach keypress mid-window\n        // aborts the retry loop honestly. The banner repaints each iteration with a\n        // live COUNTDOWN (REQ-RC-RECONNECT-TRUTH) so the operator sees rc is bounded\n        // and working, never the old apparent freeze at a static \"Reconnecting…\".\n        // establish_attach is CONNECT-ONLY: a down daemon yields DaemonDown here and\n        // is retried (the operator may bring it back) WITHOUT any WMI resurrection.\n        let target = remote_node.clone().unwrap_or_else(|| \"local daemon\".to_string());\n        let retry_started = Instant::now();\n        let reestablished = loop {\n            let elapsed = retry_started.elapsed();\n            if reconnect_expired(elapsed) {\n                break None;\n            }\n            let (cols, rows) = crossterm::terminal::size().unwrap_or((80, 24));\n            let remaining = reconnect_remaining_secs(elapsed);\n            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\n            let _ = out.flush();\n            // Operator gives up mid-reconnect: honor the detach keys.\n            if let Some(rx) = rx {\n                let mut detached = false;\n                while let Ok(msg) = rx.try_recv() {\n                    if matches!(msg, StdinMsg::Detach) {\n                        detached = true;\n                    }\n                }\n                if detached {\n                    break Some(Err(())); // detach sentinel\n                }\n            }\n            match establish_attach(endpoint_id, intent) {\n                Ok(next) => break Some(Ok(next)),\n                // Every fail shape retries until the window expires — the node/daemon\n                // may still be coming back. DaemonDown included: rc waits for the\n                // operator to restart it, it does NOT auto-start one.\n                Err(_) => std::thread::sleep(RECONNECT_PAUSE),\n            }\n        };\n        match reestablished {\n            Some(Ok(next)) => {\n                *remote_node = next.remote_node.clone();\n                est = next;\n                // Loop: fresh resize + pump. The re-serve replays the output ring,\n                // repainting the screen the banner cleared.\n            }\n            Some(Err(())) => return Ok(PumpEnd::Detached),\n            None => {\n                // Give-up copy selection (REQ-RC-RECONNECT-TRUTH): a still-down local\n                // daemon (only knowable for a LOCAL target) gets the loud \"session\n                // lost — daemon down\" copy; anything else keeps the generic\n                // didn't-reconnect copy. Remote severs never probe local daemon state.\n                let daemon_down =\n                    remote_node.is_none() && !spt_daemon::daemon::is_running();\n                return Ok(PumpEnd::ReconnectGaveUp {\n                    detail: severed_kind.to_string(),\n                    daemon_down,\n                });\n            }\n        }\n    }\n}\n\n/// How long the B-3 reconnect loop keeps re-driving `establish_attach` after a\n/// severed viewport before the plain-language give-up (REQ-RC-RECONNECT).\n/// Generous: a daemon bounce (`spt daemon stop` + the next auto-start) and a\n/// node's broker restart both land well inside it.\nconst RECONNECT_WINDOW: Duration = Duration::from_secs(30);\n/// The pause between reconnect attempts (each attempt already carries its own\n/// bounded setup deadlines — this just keeps the loop from hammering).\nconst RECONNECT_PAUSE: Duration = Duration::from_secs(1);\n\n/// The give-up decision, pulled out so it unit-tests without a 30s wait.\n// [impl->REQ-RC-RECONNECT]\nfn reconnect_expired(elapsed: Duration) -> bool {\n    elapsed >= RECONNECT_WINDOW\n}\n\n/// Seconds still left in the reconnect window, for the banner countdown\n/// (REQ-RC-RECONNECT-TRUTH). CEIL so the display reads a full `RECONNECT_WINDOW`s\n/// at the start and never flashes `0s` while the loop is still trying (0 only once\n/// the window is fully spent, where the caller has already broken to give-up).\n// [impl->REQ-RC-RECONNECT-TRUTH]\nfn reconnect_remaining_secs(elapsed: Duration) -> u64 {\n    let left = RECONNECT_WINDOW.saturating_sub(elapsed);\n    // ceil(left / 1s) via integer millis — 1..=WINDOW while any time remains, 0 at/past.\n    (left.as_millis() as u64).div_ceil(1000)\n}\n\n/// Which pump ends re-drive the viewport (B-3) vs. exit with parting copy.\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum EndClass {\n    Final,\n    Reconnect,\n}\n\n/// A severed transport is RECONNECTABLE: the broker-conn EOF class (broker\n/// bounce) and the serve-side EOF after rendered output (remote conn drop).\n/// Everything else — session exit, operator detach, displacement, the stall\n/// backstop, an attach refuse — is FINAL: re-driving those would re-attach to a\n\n[Showing lines 1544-1668 of 3214. Use :1669 to continue]"}],"details":{"truncation":{"content":"            }\r\n        }\r\n\r\n        let mut decoder = AttachDecoder::new();\r\n        let end = pump(\r\n            &mut est.brain,\r\n            est.stream_id,\r\n            &est.ops,\r\n            rx,\r\n            &mut decoder,\r\n            out,\r\n            !view, // controller drives resize\r\n            mouse_mode,\r\n            status.as_deref_mut(),\r\n        );\r\n\r\n        // Final ends (incl. hard errors) exit the loop to the parting copy.\r\n        let severed_kind = match &end {\r\n            Ok(e) if reconnect_class(e) == EndClass::Reconnect => match e {\r\n                PumpEnd::BrokerGone => \"the daemon connection dropped\",\r\n                _ => \"the connection was severed\",\r\n            },\r\n            _ => return end,\r\n        };\r\n\r\n        // Reconnect: full-screen centered banner (operator language — the internal\r\n        // detail stays off the terminal per the Q4 UX rule), then re-drive\r\n        // establish_attach inside the bounded window. A detach keypress mid-window\r\n        // aborts the retry loop honestly. The banner repaints each iteration with a\r\n        // live COUNTDOWN (REQ-RC-RECONNECT-TRUTH) so the operator sees rc is bounded\r\n        // and working, never the old apparent freeze at a static \"Reconnecting…\".\r\n        // establish_attach is CONNECT-ONLY: a down daemon yields DaemonDown here and\r\n        // is retried (the operator may bring it back) WITHOUT any WMI resurrection.\r\n        let target = remote_node.clone().unwrap_or_else(|| \"local daemon\".to_string());\r\n        let retry_started = Instant::now();\r\n        let reestablished = loop {\r\n            let elapsed = retry_started.elapsed();\r\n            if reconnect_expired(elapsed) {\r\n                break None;\r\n            }\r\n            let (cols, rows) = crossterm::terminal::size().unwrap_or((80, 24));\r\n            let remaining = reconnect_remaining_secs(elapsed);\r\n            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\r\n            let _ = out.flush();\r\n            // Operator gives up mid-reconnect: honor the detach keys.\r\n            if let Some(rx) = rx {\r\n                let mut detached = false;\r\n                while let Ok(msg) = rx.try_recv() {\r\n                    if matches!(msg, StdinMsg::Detach) {\r\n                        detached = true;\r\n                    }\r\n                }\r\n                if detached {\r\n                    break Some(Err(())); // detach sentinel\r\n                }\r\n            }\r\n            match establish_attach(endpoint_id, intent) {\r\n                Ok(next) => break Some(Ok(next)),\r\n                // Every fail shape retries until the window expires — the node/daemon\r\n                // may still be coming back. DaemonDown included: rc waits for the\r\n                // operator to restart it, it does NOT auto-start one.\r\n                Err(_) => std::thread::sleep(RECONNECT_PAUSE),\r\n            }\r\n        };\r\n        match reestablished {\r\n            Some(Ok(next)) => {\r\n                *remote_node = next.remote_node.clone();\r\n                est = next;\r\n                // Loop: fresh resize + pump. The re-serve replays the output ring,\r\n                // repainting the screen the banner cleared.\r\n            }\r\n            Some(Err(())) => return Ok(PumpEnd::Detached),\r\n            None => {\r\n                // Give-up copy selection (REQ-RC-RECONNECT-TRUTH): a still-down local\r\n                // daemon (only knowable for a LOCAL target) gets the loud \"session\r\n                // lost — daemon down\" copy; anything else keeps the generic\r\n                // didn't-reconnect copy. Remote severs never probe local daemon state.\r\n                let daemon_down =\r\n                    remote_node.is_none() && !spt_daemon::daemon::is_running();\r\n                return Ok(PumpEnd::ReconnectGaveUp {\r\n                    detail: severed_kind.to_string(),\r\n                    daemon_down,\r\n                });\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// How long the B-3 reconnect loop keeps re-driving `establish_attach` after a\r\n/// severed viewport before the plain-language give-up (REQ-RC-RECONNECT).\r\n/// Generous: a daemon bounce (`spt daemon stop` + the next auto-start) and a\r\n/// node's broker restart both land well inside it.\r\nconst RECONNECT_WINDOW: Duration = Duration::from_secs(30);\r\n/// The pause between reconnect attempts (each attempt already carries its own\r\n/// bounded setup deadlines — this just keeps the loop from hammering).\r\nconst RECONNECT_PAUSE: Duration = Duration::from_secs(1);\r\n\r\n/// The give-up decision, pulled out so it unit-tests without a 30s wait.\r\n// [impl->REQ-RC-RECONNECT]\r\nfn reconnect_expired(elapsed: Duration) -> bool {\r\n    elapsed >= RECONNECT_WINDOW\r\n}\r\n\r\n/// Seconds still left in the reconnect window, for the banner countdown\r\n/// (REQ-RC-RECONNECT-TRUTH). CEIL so the display reads a full `RECONNECT_WINDOW`s\r\n/// at the start and never flashes `0s` while the loop is still trying (0 only once\r\n/// the window is fully spent, where the caller has already broken to give-up).\r\n// [impl->REQ-RC-RECONNECT-TRUTH]\r\nfn reconnect_remaining_secs(elapsed: Duration) -> u64 {\r\n    let left = RECONNECT_WINDOW.saturating_sub(elapsed);\r\n    // ceil(left / 1s) via integer millis — 1..=WINDOW while any time remains, 0 at/past.\r\n    (left.as_millis() as u64).div_ceil(1000)\r\n}\r\n\r\n/// Which pump ends re-drive the viewport (B-3) vs. exit with parting copy.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum EndClass {\r\n    Final,\r\n    Reconnect,\r\n}\r\n\r\n/// A severed transport is RECONNECTABLE: the broker-conn EOF class (broker\r\n/// bounce) and the serve-side EOF after rendered output (remote conn drop).\r\n/// Everything else — session exit, operator detach, displacement, the stall\r\n/// backstop, an attach refuse — is FINAL: re-driving those would re-attach to a\r","truncated":true,"truncatedBy":"lines","totalLines":1671,"totalBytes":5855,"outputLines":125,"outputBytes":5855,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"fn attach_viewport(\n…\n) -> Result<PumpEnd, String> {\n    loop {\n…\n        if !view {\n            if let Ok((cols, rows)) = crossterm::terminal::size() {\n…\n            }\r\n        }\r\n\r\n        let mut decoder = AttachDecoder::new();\r\n        let end = pump(\r\n            &mut est.brain,\r\n            est.stream_id,\r\n            &est.ops,\r\n            rx,\r\n            &mut decoder,\r\n            out,\r\n            !view, // controller drives resize\r\n            mouse_mode,\r\n            status.as_deref_mut(),\r\n        );\r\n\r\n        // Final ends (incl. hard errors) exit the loop to the parting copy.\r\n        let severed_kind = match &end {\r\n            Ok(e) if reconnect_class(e) == EndClass::Reconnect => match e {\r\n                PumpEnd::BrokerGone => \"the daemon connection dropped\",\r\n                _ => \"the connection was severed\",\r\n            },\r\n            _ => return end,\r\n        };\r\n\r\n        // Reconnect: full-screen centered banner (operator language — the internal\r\n        // detail stays off the terminal per the Q4 UX rule), then re-drive\r\n        // establish_attach inside the bounded window. A detach keypress mid-window\r\n        // aborts the retry loop honestly. The banner repaints each iteration with a\r\n        // live COUNTDOWN (REQ-RC-RECONNECT-TRUTH) so the operator sees rc is bounded\r\n        // and working, never the old apparent freeze at a static \"Reconnecting…\".\r\n        // establish_attach is CONNECT-ONLY: a down daemon yields DaemonDown here and\r\n        // is retried (the operator may bring it back) WITHOUT any WMI resurrection.\r\n        let target = remote_node.clone().unwrap_or_else(|| \"local daemon\".to_string());\r\n        let retry_started = Instant::now();\r\n        let reestablished = loop {\r\n            let elapsed = retry_started.elapsed();\r\n            if reconnect_expired(elapsed) {\r\n                break None;\r\n            }\r\n            let (cols, rows) = crossterm::terminal::size().unwrap_or((80, 24));\r\n            let remaining = reconnect_remaining_secs(elapsed);\r\n            let _ = out.write_all(&reconnect_banner_bytes(rows, cols, &target, remaining));\r\n            let _ = out.flush();\r\n            // Operator gives up mid-reconnect: honor the detach keys.\r\n            if let Some(rx) = rx {\r\n                let mut detached = false;\r\n                while let Ok(msg) = rx.try_recv() {\r\n                    if matches!(msg, StdinMsg::Detach) {\r\n                        detached = true;\r\n                    }\r\n                }\r\n                if detached {\r\n                    break Some(Err(())); // detach sentinel\r\n                }\r\n            }\r\n            match establish_attach(endpoint_id, intent) {\r\n                Ok(next) => break Some(Ok(next)),\r\n                // Every fail shape retries until the window expires — the node/daemon\r\n                // may still be coming back. DaemonDown included: rc waits for the\r\n                // operator to restart it, it does NOT auto-start one.\r\n                Err(_) => std::thread::sleep(RECONNECT_PAUSE),\r\n            }\r\n        };\r\n        match reestablished {\r\n            Some(Ok(next)) => {\r\n                *remote_node = next.remote_node.clone();\r\n                est = next;\r\n                // Loop: fresh resize + pump. The re-serve replays the output ring,\r\n                // repainting the screen the banner cleared.\r\n            }\r\n            Some(Err(())) => return Ok(PumpEnd::Detached),\r\n            None => {\r\n                // Give-up copy selection (REQ-RC-RECONNECT-TRUTH): a still-down local\r\n                // daemon (only knowable for a LOCAL target) gets the loud \"session\r\n                // lost — daemon down\" copy; anything else keeps the generic\r\n                // didn't-reconnect copy. Remote severs never probe local daemon state.\r\n                let daemon_down =\r\n                    remote_node.is_none() && !spt_daemon::daemon::is_running();\r\n                return Ok(PumpEnd::ReconnectGaveUp {\r\n                    detail: severed_kind.to_string(),\r\n                    daemon_down,\r\n                });\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/// How long the B-3 reconnect loop keeps re-driving `establish_attach` after a\r\n/// severed viewport before the plain-language give-up (REQ-RC-RECONNECT).\r\n/// Generous: a daemon bounce (`spt daemon stop` + the next auto-start) and a\r\n/// node's broker restart both land well inside it.\r\nconst RECONNECT_WINDOW: Duration = Duration::from_secs(30);\r\n/// The pause between reconnect attempts (each attempt already carries its own\r\n/// bounded setup deadlines — this just keeps the loop from hammering).\r\nconst RECONNECT_PAUSE: Duration = Duration::from_secs(1);\r\n\r\n/// The give-up decision, pulled out so it unit-tests without a 30s wait.\r\n// [impl->REQ-RC-RECONNECT]\r\nfn reconnect_expired(elapsed: Duration) -> bool {\r\n    elapsed >= RECONNECT_WINDOW\r\n}\r\n\r\n/// Seconds still left in the reconnect window, for the banner countdown\r\n/// (REQ-RC-RECONNECT-TRUTH). CEIL so the display reads a full `RECONNECT_WINDOW`s\r\n/// at the start and never flashes `0s` while the loop is still trying (0 only once\r\n/// the window is fully spent, where the caller has already broken to give-up).\r\n// [impl->REQ-RC-RECONNECT-TRUTH]\r\nfn reconnect_remaining_secs(elapsed: Duration) -> u64 {\r\n    let left = RECONNECT_WINDOW.saturating_sub(elapsed);\r\n    // ceil(left / 1s) via integer millis — 1..=WINDOW while any time remains, 0 at/past.\r\n    (left.as_millis() as u64).div_ceil(1000)\r\n}\r\n\r\n/// Which pump ends re-drive the viewport (B-3) vs. exit with parting copy.\r\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\r\nenum EndClass {\r\n    Final,\r\n    Reconnect,\r\n}\r\n\r\n/// A severed transport is RECONNECTABLE: the broker-conn EOF class (broker\r\n/// bounce) and the serve-side EOF after rendered output (remote conn drop).\r\n/// Everything else — session exit, operator detach, displacement, the stall\r\n/// backstop, an attach refuse — is FINAL: re-driving those would re-attach to a\r","startLine":1519,"lineNumbers":[1519,null,1529,1530,null,1536,1537,null,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605,1606,1607,1608,1609,1610,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1632,1633,1634,1635,1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646,1647,1648,1649,1650,1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662,1663,1664,1665,1666,1667,1668]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt\\src\\rc.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":3214,"totalBytes":5855,"outputLines":125,"outputBytes":5855,"shownRange":{"start":1544,"end":1668},"nextOffset":1669}}},"isError":false,"timestamp":1784247723525}}
{"type":"message","id":"dcb2a529","parentId":"7a31e4ea","timestamp":"2026-07-17T00:22:13.438Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing connection poison timing and versions**\n\n**Verifying connection log behavior across versions**"},{"type":"toolCall","id":"call_Ihuo4tsAdwEfcWpnY8UlPBG5|fc_0ceecce325b7fe2d016a5975b479a08198bd3270d157727ec0","name":"grep","arguments":{"i":"Dating writer and v34 commits","pattern":"fc048bb|ad7fde8|2a110ee|8425357|571d6a5|eb9fd864","path":".git/logs","selector":null,"case":false,"gitignore":false,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4154,"output":412,"cacheRead":84480,"cacheWrite":0,"totalTokens":89046,"reasoningTokens":332,"cost":{"input":0.02077,"output":0.01236,"cacheRead":0.04224,"cacheWrite":0,"total":0.07536999999999999}},"stopReason":"toolUse","timestamp":1784247723539,"responseId":"resp_0ceecce325b7fe2d016a5975abc0cc81989f763bf7534565e6","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5975ad48e08198ad93da80013378dd","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXW0rAU2nJR0o8a5X70RK5dyS5CpqNmRbsvgoKMkHdHwr8Ki2w8LIpD1j8lsODZ9DMHHeHDx0tabCYI79Y6RkRtWlgILrqyeUnFnNTEPkSoNt1ZXCxc_Enfm1OobPXMUfNxqxm_3R4fwSsoeIt-PiwIIvwFPxL2QNbr_Pd1foxPJtj7GFn_JQXa7SyL_xRZg-_ZSHB_LhDwUffrvZlrZGyw4NITCR2RyWw6mB0ZPAFmxPmT5NUMclU5lVNWqLYp-HH7CFOrxpxCHuJsehZNyuuRhpvFbPfkLyQoaFHVkh_4jyHuBK72J0d6BDLHdoOgemIbnNXDbQcFriYliEvWNOIn5XC7X93JiLCsCqoSNNEJQheTyAfBvmKXyW9tRnKkVli9TSQLHzsoP2-ntozc2xPBGoxg0O2TVRDaeS5uQhnJ_xSv8FBr772pW2NY7Yyr4CeDCQMrKlrPMLX_rV_i12HBK-s2-_Jh8pEbpYPEmxa0fnlb3684iNsZc40DSi1j5LVf7Uzco1_zeu9INOdP25stuj23Xyd2TpESSnNdVxXBzJQOrMyUJPCpr_5ud3tDY0kkTM37ELiS72lTQIAnEWzZVYGIOY9d7tmudZGXZOuImz-TjbeAN2ooSAioSIGEU6TvHREOIS3Y6vznlzNQN6PfxcP62rJhWeghuAGetVJ4ZceOwAiUlf_6-Nyw5g-4Qt0IqFvZHLrcJwldDCgJ38874Qendz0sZ-bhiNkRpWDKazN8vnPTyhtwRde05Ak4oEVLiWVeL7kmv0O3DSpwyUbFN6mnJPnTUg5ReD50fDkKG42yGY2Ft3Oj8j073Ue1LuluC4WXyGrkMzOw8kK5CvXHm8SQlhjGwqnhaKhKTl5NagNqd-bT37WG8_bkZd-8yfnVJheaN96F4wdYT8Lw4_Hq5uwYYBxJkxfS4yHXpRfiC8hcblNDHKUcB6DQT6AEPNz6pGsbOO8otqTa-e5hL1hcfygVdc4pA5YApldqmyFTkAZ8CybrlKFxs_iTIOBMAwqBBYsQkwN4i4-8qK3t5y3WZawo3VrkEXdNYE7u5k5Xt5ZakwBOAyTm_PCtX_QcYkSCHy0eJAA6PJ6W25Ur2kAkjZARyXsJQd8hGguYKldC6p0S8z_Sxi3j3hEGtNXtxhCWnNISFA1a6hSnltmpTj5jsEfZz3Ed6wjXd4oE3g2Da2DNaJgmSQ3f3TO4jjKLOB0Eo-XzrM3wEj_hTacz4Te2DwiepjoHycGknNMspP5XOiDDZAteig_iCDP-ud-GW3PQAhcTBcKc075wOwz5lU7FpQA6OOGdnNTzhJlMRy_-nEVyh7lkYXJUtM01Rlhlt9EDeQYL0Fa2Q36aSSoRK12WWxfduqYr-c9h5vt8VWTPVYrT3l_KrmTtNoZg2rRJUhA3uqF5VorGaaiSl9pIkXRYfO2OlpUaaMS3ygHiiBtSRGUXGWrUM1ZLVfe3DSeviyCQ8QKDNukPg9ib1pjQNBd7k9LfaRy0_AhJsZ-UQfw1eMmjl00_7H8bmEWUzFariO0ulzGkB9th7d0EP5p9V9r5VVnsgQEKF7AlclVEXlXeyfYcZvfWjWG8sGY5QWqVroWlcmfV7h6bxcP2ieYJ9hHbitp-Whpj-6w0gSGJ2Hx31KZNcvq7J86hapMdyAbQLQ5JL2EyomEdoAxJCZSvMePYjZpp76v_qh82yHaHNIwbcRntToSdkO6lzJEMr0drV-fN8Z73trMbc6_d5OeVHggYcaPnnuEr4MVofIPdBgT1R2iHd0WhylX6jvWHYcBHx7sj5RpVkEu5RtSzK9mxiSd8pBOPcNeJQq7qdDXSfvpNlxfmZgtNrciG2COTT74x4w1FAvsP-wp__OI6cg-U2LBMCqm4h4uRe3vdx3fQ03DGG_cAnYHndVBPdHE9O55fDQV48iJkRCwHl63_WrMhTc5EDNMP9t5Rd0085bc3hTJtL2UjwU2ZtwUPycVvq7wM3_I_slYwkDvmxzAvqLfUzmWguBR-2hW5GgJiKFvBNzVA8RAmHAw1-vMHVjGSuMP6eA-QhGEd8Bfn1FfXI4Q82C91r5J8FV5lY2sg0EvHt9I2YpeXr1ZlRahC6RVBLromVWuKmRZPdniaLMTAsWexsk4QzCstRVKM_qBtalIdaHe1L8CckbIBKxUEVw-KdLCmNHrW1vcd64yg_HPLeRpXwaTQKS6iYTxhLv8UJ5ILpUoXH_JEfYgaUy2xRbBe_pvYYu6IaWQbEGpyrzVo5g-XspeCN8Wyi7YUNNtBFZV-z9DGkFW2j2EQKk2xPm3NeBUBX8bKNfPD1cfdicfTV_-zsAKwm40yHmeQm7N5FqYfLekSOMeBWOUbfhx6AbpO6vTRpgxQHNO72_r0OTXIzmzWGywPz6vyDcG2xwa3JhjCw7cd7_nPAaY7wkBfpKuOK3dek5RLyi_g9BGkVwOVsLXadjOQDK59j4FTpF0YIecvrdVNpgpKJm7D_PXnyz9WRkOu6l6TfKmMgf7zx559sOk-9sYfJqXo_6MHJWZ55KFCzwKW8i_Ws0VQINW4xlKERO0uy32TNgOunAFs6KgbdLnjIftk2K-6ZNj9oU0jyyyiU-NejczBCS6VDYzNvwvhfAepqnNrOxg8ZNs72gWUD_tqT5oQ1f2cXuB-j-hbHPDRmUWgqbwAp1EsIwBzIGtDEY6i3J9Gi9lClqFBWX1C_eJzaXnNkX_cdXSQgl0WPlVj4Mm25SaOsRFpBKXxpx-KmL5kT013Mz1xLBF9AntqCQyvXmYRHxB2uXs1oE_eGHvfMUSQloAweh59ZvYZnFvzazG1f3F4I","summary":[{"type":"summary_text","text":"**Analyzing connection poison timing and versions**"},{"type":"summary_text","text":"**Verifying connection log behavior across versions**"}]},{"id":"fc_0ceecce325b7fe2d016a5975b479a08198bd3270d157727ec0","type":"function_call","status":"completed","arguments":"{\"i\":\"Dating writer and v34 commits\",\"pattern\":\"fc048bb|ad7fde8|2a110ee|8425357|571d6a5|eb9fd864\",\"path\":\".git/logs\",\"selector\":null,\"case\":false,\"gitignore\":false,\"skip\":null}","call_id":"call_Ihuo4tsAdwEfcWpnY8UlPBG5","name":"grep"}]},"duration":9898.856600001454,"ttft":1737.8550999984145,"contextSnapshot":{"promptTokens":88634,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Ihuo4tsAdwEfcWpnY8UlPBG5|fc_0ceecce325b7fe2d016a5975b479a08198bd3270d157727ec0","toolName":"grep","startedAt":"2026-07-17T00:22:13.438Z","args":{"path":".git/logs"},"intent":"Dating writer and v34 commits"},"id":"0c1e1783","parentId":"dcb2a529","timestamp":"2026-07-17T00:22:13.438Z"}
{"type":"message","id":"9e6ab388","parentId":"0c1e1783","timestamp":"2026-07-17T00:22:13.455Z","message":{"role":"toolResult","toolCallId":"call_Ihuo4tsAdwEfcWpnY8UlPBG5|fc_0ceecce325b7fe2d016a5975b479a08198bd3270d157727ec0","toolName":"grep","content":[{"type":"text","text":"# .git/logs/\n## HEAD\n 1396|315caf7df04773ae11161da189d2f10dc11a2e1b 4e0916cdf223756ce368fca934d3b7c7fd05688e Reavo End <decidel@gmail.com> 1783735239 -0700\tcommit: docs(msg-identity): W6 F-039 REQ moves — RCA-lock addendum + attribution/harness mints (doyle W6 LOCK)\n*1397|4e0916cdf223756ce368fca934d3b7c7fd05688e fc048bb41560b629c8cb1d158f8587f67f895e39 Reavo End <decidel@gmail.com> 1783736665 -0700\tcommit: feat(msg-identity): W6 F-039 legs a-d — poison token split + conn attribution + canary isolation\n*1398|fc048bb41560b629c8cb1d158f8587f67f895e39 8951793a58dc7db5cb05c38b322e604aaf080be2 Reavo End <decidel@gmail.com> 1783736859 -0700\tcommit: test(msg-identity): W6 rider — r4 gate accepts poison-beats-evict tally race (doyle ruling)\n 1399|8951793a58dc7db5cb05c38b322e604aaf080be2 fba81ec78484804b341e5ce726bf3dd9fbddf82f Reavo End <decidel@gmail.com> 1783737642 -0700\tcommit: test(msg-identity): W6 F-039 leg e — black-holed-controller lifecycle harness\n 1400|fba81ec78484804b341e5ce726bf3dd9fbddf82f 06a2d74d0740577f740e0d94f31dc78982dfbcca Reavo End <decidel@gmail.com> 1783739468 -0700\tcommit: test(msg-identity): W6 leg-e rework — survivor-identity snapshot closes the leaked-handler false-green (doyle R1+R2)\n 1401|06a2d74d0740577f740e0d94f31dc78982dfbcca 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783740437 -0700\tcheckout: moving from build/msg-identity-w6-f039 to main\n...\n 1457|3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197660 -0700\tcheckout: moving from main to build/redispatch-truth-w1\n*1458|3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200151 -0700\tcommit: build(redispatch-truth-w1): broker stream-table lifecycle truth — retire, durable opener, retryable claims\n*1459|ad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201516 -0700\tcommit: fix(redispatch-truth-w1): gate round 1 — finished ATTACH rows are terminal-unserved; request/reply FIN stays servable\n*1460|2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204304 -0700\tcommit: fix(redispatch-truth-w1): gate round 2 — retirement is eligibility-visibility, never teardown\n*1461|84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204456 -0700\tcommit: docs(redispatch-truth-w1): ADR-0038 — pin the round-2 retirement ruling (eligibility-visibility, never teardown)\n*1462|571d6a55e9949ce1b96aef1ba00f5e007a0a9521 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784206456 -0700\tcheckout: moving from build/redispatch-truth-w1 to main\n 1463|3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n 1464|01d2d18cb3196313eb04e6a1fa03e28437fb5056 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tcheckout: moving from main to release/v0.34.0\n*1465|01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n*1466|eb9fd8643921930ce21cc7ef0751a27360fc14bc 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784211926 -0700\tcheckout: moving from release/v0.34.0 to main\n 1467|01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\n 1468|b7b074c2509c1015fe341185662b4622d661f274 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784236538 -0700\tcheckout: moving from main to fix/changelog-0330-heading\n 1469|b7b074c2509c1015fe341185662b4622d661f274 d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 Reavo End <decidel@gmail.com> 1784236564 -0700\tcommit: docs(changelog): restore consumed '## [0.33.0]' heading\n\n## refs/\n\n### heads/release/\n#### v0.34.0\n 1|0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tbranch: Created from 01d2d18\n*2|01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n\n### remotes/origin/\n\n#### build/\n##### redispatch-truth-w1\n*1|0000000000000000000000000000000000000000 ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200461 -0700\tupdate by push\n*2|ad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201519 -0700\tupdate by push\n*3|2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204306 -0700\tupdate by push\n*4|84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204465 -0700\tupdate by push\n\n#### release/\n##### v0.34.0\n*1|0000000000000000000000000000000000000000 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206670 -0700\tupdate by push"}],"details":{"scopePath":".git/logs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\.git\\logs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":15,"fileCount":4,"files":[".git/logs/HEAD",".git/logs/refs/heads/release/v0.34.0",".git/logs/refs/remotes/origin/build/redispatch-truth-w1",".git/logs/refs/remotes/origin/release/v0.34.0"],"fileMatches":[{"path":".git/logs/HEAD","count":9},{"path":".git/logs/refs/heads/release/v0.34.0","count":1},{"path":".git/logs/refs/remotes/origin/build/redispatch-truth-w1","count":4},{"path":".git/logs/refs/remotes/origin/release/v0.34.0","count":1}],"truncated":false,"displayContent":"# .git/logs/\n## HEAD\n 1396│315caf7df04773ae11161da189d2f10dc11a2e1b 4e0916cdf223756ce368fca934d3b7c7fd05688e Reavo End <decidel@gmail.com> 1783735239 -0700\tcommit: docs(msg-identity): W6 F-039 REQ moves — RCA-lock addendum + attribution/harness mints (doyle W6 LOCK)\n*1397│4e0916cdf223756ce368fca934d3b7c7fd05688e fc048bb41560b629c8cb1d158f8587f67f895e39 Reavo End <decidel@gmail.com> 1783736665 -0700\tcommit: feat(msg-identity): W6 F-039 legs a-d — poison token split + conn attribution + canary isolation\n*1398│fc048bb41560b629c8cb1d158f8587f67f895e39 8951793a58dc7db5cb05c38b322e604aaf080be2 Reavo End <decidel@gmail.com> 1783736859 -0700\tcommit: test(msg-identity): W6 rider — r4 gate accepts poison-beats-evict tally race (doyle ruling)\n 1399│8951793a58dc7db5cb05c38b322e604aaf080be2 fba81ec78484804b341e5ce726bf3dd9fbddf82f Reavo End <decidel@gmail.com> 1783737642 -0700\tcommit: test(msg-identity): W6 F-039 leg e — black-holed-controller lifecycle harness\n 1400│fba81ec78484804b341e5ce726bf3dd9fbddf82f 06a2d74d0740577f740e0d94f31dc78982dfbcca Reavo End <decidel@gmail.com> 1783739468 -0700\tcommit: test(msg-identity): W6 leg-e rework — survivor-identity snapshot closes the leaked-handler false-green (doyle R1+R2)\n 1401│06a2d74d0740577f740e0d94f31dc78982dfbcca 55556482ebca6a2d3c6c453edcbc9b261eb27c15 Reavo End <decidel@gmail.com> 1783740437 -0700\tcheckout: moving from build/msg-identity-w6-f039 to main\n     │...\n 1457│3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784197660 -0700\tcheckout: moving from main to build/redispatch-truth-w1\n*1458│3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200151 -0700\tcommit: build(redispatch-truth-w1): broker stream-table lifecycle truth — retire, durable opener, retryable claims\n*1459│ad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201516 -0700\tcommit: fix(redispatch-truth-w1): gate round 1 — finished ATTACH rows are terminal-unserved; request/reply FIN stays servable\n*1460│2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204304 -0700\tcommit: fix(redispatch-truth-w1): gate round 2 — retirement is eligibility-visibility, never teardown\n*1461│84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204456 -0700\tcommit: docs(redispatch-truth-w1): ADR-0038 — pin the round-2 retirement ruling (eligibility-visibility, never teardown)\n*1462│571d6a55e9949ce1b96aef1ba00f5e007a0a9521 3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f Reavo End <decidel@gmail.com> 1784206456 -0700\tcheckout: moving from build/redispatch-truth-w1 to main\n 1463│3d9f2ed4a38bdc6dbe4e7157bd2b35d049a79d9f 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206458 -0700\tpull origin main: Fast-forward\n 1464│01d2d18cb3196313eb04e6a1fa03e28437fb5056 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tcheckout: moving from main to release/v0.34.0\n*1465│01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n*1466│eb9fd8643921930ce21cc7ef0751a27360fc14bc 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784211926 -0700\tcheckout: moving from release/v0.34.0 to main\n 1467│01d2d18cb3196313eb04e6a1fa03e28437fb5056 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784211927 -0700\tpull --ff-only: Fast-forward\n 1468│b7b074c2509c1015fe341185662b4622d661f274 b7b074c2509c1015fe341185662b4622d661f274 Reavo End <decidel@gmail.com> 1784236538 -0700\tcheckout: moving from main to fix/changelog-0330-heading\n 1469│b7b074c2509c1015fe341185662b4622d661f274 d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7 Reavo End <decidel@gmail.com> 1784236564 -0700\tcommit: docs(changelog): restore consumed '## [0.33.0]' heading\n\n## refs/\n\n### heads/release/\n#### v0.34.0\n 1│0000000000000000000000000000000000000000 01d2d18cb3196313eb04e6a1fa03e28437fb5056 Reavo End <decidel@gmail.com> 1784206466 -0700\tbranch: Created from 01d2d18\n*2│01d2d18cb3196313eb04e6a1fa03e28437fb5056 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206668 -0700\tcommit: release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1\n\n### remotes/origin/\n\n#### build/\n##### redispatch-truth-w1\n*1│0000000000000000000000000000000000000000 ad7fde8acb97b5c839316121d341375b077092df Reavo End <decidel@gmail.com> 1784200461 -0700\tupdate by push\n*2│ad7fde8acb97b5c839316121d341375b077092df 2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 Reavo End <decidel@gmail.com> 1784201519 -0700\tupdate by push\n*3│2a110eed6f97d3dab96ed301d1b6d1df9a2cc002 84253572cd5e0cef57201ba671a8c201c6dbbdd8 Reavo End <decidel@gmail.com> 1784204306 -0700\tupdate by push\n*4│84253572cd5e0cef57201ba671a8c201c6dbbdd8 571d6a55e9949ce1b96aef1ba00f5e007a0a9521 Reavo End <decidel@gmail.com> 1784204465 -0700\tupdate by push\n\n#### release/\n##### v0.34.0\n*1│0000000000000000000000000000000000000000 eb9fd8643921930ce21cc7ef0751a27360fc14bc Reavo End <decidel@gmail.com> 1784206670 -0700\tupdate by push"},"isError":false,"timestamp":1784247733455}}
{"type":"message","id":"6419c991","parentId":"9e6ab388","timestamp":"2026-07-17T00:22:21.457Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Analyzing release tag and merge commit**\n\n**Addressing line number consistency issue**"},{"type":"toolCall","id":"call_HWwx4Qa9It4s7R5CJike5K0o|fc_0ceecce325b7fe2d016a5975bc7fc0819898ba050459876bed","name":"grep","arguments":{"i":"Confirming retirement visibility semantics","pattern":"retired|retain\\(|NetStreamsReply","path":"crates/spt-daemon/src/nethost.rs","selector":"120-285,1500-1650","case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":5808,"output":305,"cacheRead":85504,"cacheWrite":0,"totalTokens":91617,"reasoningTokens":230,"cost":{"input":0.029040000000000003,"output":0.00915,"cacheRead":0.042752,"cacheWrite":0,"total":0.080942}},"stopReason":"toolUse","timestamp":1784247733467,"responseId":"resp_0ceecce325b7fe2d016a5975b5c3e08198add65cdbe7187c78","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5975b74eac819886726af9f0af0770","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXW8R0eDXHOk_n-ZV6Vq4iwXKgJaxjx8i-CPYkpFUYD-hlyIf87KqP6JK8vQTCGOnyX4ZINuurGwKI7BtDCXF4q0beqltCBzX6NTrfq2eHmF4fXVjy2CgVQsChmRA8Nkcw3c1KcaUi9Tg9TnYcBSZ-qcmAof7mSaI92FmaIVy8eO3lSgQywZD69O8xZPLvYQsApTTyVJusxSIEYb65HnYsl8-CkCT1mN2o6Vwg9ERC59YsTzBHBEU_YBKIVz8k8kxX94Wuj8L58l3oxn0wtO-SUF79GJUdGCCVcdrX2q0AneIJ9fNIxElUHCNiFbjiW7zMMMG1eRLi4yrkmDqMz6Zfe-RhoWDtseHfzAZX0P4HF-GFWf0SfJ6rb4v3Ij-AtKTDggEUBNY5grgrlfjQ4OBfR2WWyKYMlmL3TNM76EDTEPEV7QxcCq9_SF5LKkjqx-9QuhU_ynbi4sH4VON6mt6T7pyftGYC6GwMamtrUiOfEDysvV7xDE8cA0bGXLdbYthyCryCMK-_wGvRANYM7o4RVx03Yub0x0wYVf7-5PWZNFJEwgwYEy_mDcjmGbSognF9v920FbaZcxYWjbQOAmhXpNKV62qdlV6exbixHZorixaiusPUV2hsvcdvbQiniNo9OSc4Sdt_3Rfhmsq57Vfemi8Ca6FUJc8kAlmCrzOgGCOZMcQfPjLwaccWt7Fp7EjILbCFOu0IGuBL6OKH0utnLhU7SXmzTGacPPbdL0vAVTYme1uD7kpQVZYeCaLIs5O6gkLCDHKhqZ9-rziAK7-5E6Gkncuah4zNuEuzahr1APsL5vBIuDpEJxnhMvPDFji1K6R8GecPHgMQa-3SvXHR3ORx-LsDMXPJcIFhXiFQsCb0JzubtdtHezwfIL5KS9c1cvzTfQVfXzKXZQfv69_jNvefkXwZEzxJbWuWpS_Vma7fS2kFZ6MHiXdqh9aJCrcewJ_bjfWlxRnwM4Wgb8nsr8wvCeJnfpsLyvm4IEJBY4EE8KnY1EfvBTJi0PxaP4jDy4peyUJRZOU7T7Ob9FsFCUWOELDI8WVTF0Usv-Zzenr9fHkDAP79SpFoczeHjl5lYaWWQjqHE6awnklllmQDTNkKwr-YTYXDFkR2VnAbDlEDxx_wyIHdUbHKzwY5k1IkBQ8czjFna_tPO_8g4HQ0mkqrFYTeN29pMDOii2RwJ1h5o1ZWY7u9bzRg_zk5eLS1w1IOCkDlouXEnjw2ZdaTl-dXga8b9VRsNFgwENTzNjmo3U3ciM_xYH6E6rjouj9W1XfIN3KvK_b6O0Q_C5wHoUhLeSMpMLBgSsfGL969Jqe3Vv9YL6ZedEm4oQNrdNO_K6Bdn0sYYBha0v0CZ9rHjq4pKA4YMlrK1FHZI1qpLdgMVkIRkQmn9jzYFdkJ1W2MaVuJXm1akAznzvLsMFBsKDeoOeIiznI07Y_SbdEimbW1o-znqmaks8MjuiXzTudMjfrjJ0LUWlj4yz3AXXM4OIsI9Nd_-wXFIiSf6kspifec93kI3ns_UnmmI18Vh-ciixyEatKZsI4ElAKtP5ZBZp6VbhMXbFjT4BH-nQ-pfOZ8dFt6vloL9DQ1Czw3-w3VMHM5UleOprIkW3XAq1U_R7l393wjpSax8SU03SStNQD4unJhh8p7-FgBLi4JCHtse84Nu6EqOk_8js42w16f_k_GH3jfZURUN5dVKW5ih5qvKvYUi9xwWD-pwX9dBKyYrGxjNHEIvraVqMkyU1lerSmallFhtjlQc_BlgWbkUsZCkCfhyjsV8BwEXGmZ6FGsK7uV7SDukhVS6Pk7EfkVyVB3zFl0q6oSkTgNw-c9FL-AKBIQXfJEfcJYv88wYy1WskpY-iqqcuY8PkWdQPNlclOTJjoUGPPoJ2ZkUCeH39XWuwwdHYDtoeNXmjVOp50N1k3bLt_Ukq3sMtgjCByAMMpv2i_C2pcsSKruNxWas3woD6LnnywHkIamHdzC1Cu-0AupmioCr0FoZ-JiwQzmPplje4p3CFfC5ULeGCuK4_8S3_tTnFc_tu2kZ0k2grD02eP4q3WPE3yo6nGEqBQ93nVVCOKIOO2Iu6w_VNubXuJgR5a11jgY7iQF7NV78XWFDQWYmoHl4MfJUVIh4enJ6DG9qzgsO4VsQqnEVqfNpAtApozjsJqwwKiaGzHYpMB5wrL_57gBdNCWRXshEHpmYUpjdov-jEB6KwCvpxICdttIybIIb9yOc_1e3O0_5Xgqv4Zp48VytcagQhhF84DSi9aLJ7uPQTeuepvtOyp9XeAcbhDlpOOnF7Q_uVvBfApUSe4GMyB4hAeW3ogNwkgZhJPw==","summary":[{"type":"summary_text","text":"**Analyzing release tag and merge commit**"},{"type":"summary_text","text":"**Addressing line number consistency issue**"}]},{"id":"fc_0ceecce325b7fe2d016a5975bc7fc0819898ba050459876bed","type":"function_call","status":"completed","arguments":"{\"i\":\"Confirming retirement visibility semantics\",\"pattern\":\"retired|retain\\\\(|NetStreamsReply\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"120-285,1500-1650\",\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_HWwx4Qa9It4s7R5CJike5K0o","name":"grep"}]},"duration":7975.014200001955,"ttft":1831.7213000059128,"contextSnapshot":{"promptTokens":91312,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_HWwx4Qa9It4s7R5CJike5K0o|fc_0ceecce325b7fe2d016a5975bc7fc0819898ba050459876bed","toolName":"grep","startedAt":"2026-07-17T00:22:21.458Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Confirming retirement visibility semantics"},"id":"7d1fd0c9","parentId":"6419c991","timestamp":"2026-07-17T00:22:21.458Z"}
{"type":"message","id":"23a2f3c1","parentId":"7d1fd0c9","timestamp":"2026-07-17T00:22:21.463Z","message":{"role":"toolResult","toolCallId":"call_HWwx4Qa9It4s7R5CJike5K0o|fc_0ceecce325b7fe2d016a5975bc7fc0819898ba050459876bed","toolName":"grep","content":[{"type":"text","text":" 1534|            .iter()\n*1535|            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\n 1536|            .map(|(id, e)| {\n 1537|                let log = e.log.lock().unwrap();\n 1538|                NetStreamInfo {\n...\n 1557|    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n*1558|    /// reply sends when it removed the entry (see the `retired` field note).\n 1559|    /// Physical teardown stays with the conn-close sweep. Returns whether this\n*1560|    /// call newly retired the row (idempotent; absent row = false).\n 1561|    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1562|    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1563|        let Some(entry) = self\n...\n 1573|        let newly = !entry\n*1574|            .retired\n 1575|            .swap(true, std::sync::atomic::Ordering::SeqCst);\n 1576|        if newly {\n*1577|            // A retired exchange's request bytes are dead weight — free the\n 1578|            // ring; the pinned opener and cursors stay (cheap, bounded).\n 1579|            entry.log.lock().unwrap().ring.clear();\n 1580|        }\n...\n 1590|        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\n*1591|        // A retired row answers no-such-stream: classification exists only to\n*1592|        // serve, and a retired exchange must never be served again.\n*1593|        if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {\n 1594|            return None;\n 1595|        }\n 1596|        let line = entry.log.lock().unwrap().opener_line();"}],"details":{"scopePath":"crates/spt-daemon/src/nethost.rs","searchPath":"C:/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/nethost.rs","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":8,"fileCount":1,"files":["crates/spt-daemon/src/nethost.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/nethost.rs","count":8}],"truncated":false,"displayContent":" 1534│            .iter()\n*1535│            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\n 1536│            .map(|(id, e)| {\n 1537│                let log = e.log.lock().unwrap();\n 1538│                NetStreamInfo {\n     │...\n 1557│    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n*1558│    /// reply sends when it removed the entry (see the `retired` field note).\n 1559│    /// Physical teardown stays with the conn-close sweep. Returns whether this\n*1560│    /// call newly retired the row (idempotent; absent row = false).\n 1561│    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n 1562│    pub fn retire_stream(&self, stream_id: u64) -> bool {\n 1563│        let Some(entry) = self\n     │...\n 1573│        let newly = !entry\n*1574│            .retired\n 1575│            .swap(true, std::sync::atomic::Ordering::SeqCst);\n 1576│        if newly {\n*1577│            // A retired exchange's request bytes are dead weight — free the\n 1578│            // ring; the pinned opener and cursors stay (cheap, bounded).\n 1579│            entry.log.lock().unwrap().ring.clear();\n 1580│        }\n     │...\n 1590│        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\n*1591│        // A retired row answers no-such-stream: classification exists only to\n*1592│        // serve, and a retired exchange must never be served again.\n*1593│        if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {\n 1594│            return None;\n 1595│        }\n 1596│        let line = entry.log.lock().unwrap().opener_line();"},"isError":false,"timestamp":1784247741462}}
{"type":"message","id":"e02e3d71","parentId":"23a2f3c1","timestamp":"2026-07-17T00:22:26.612Z","message":{"role":"assistant","content":[{"type":"toolCall","id":"call_nwdmstQmi4APhe4iWp6cAg6E|fc_0ceecce325b7fe2d016a5975c11f2c8198a813d0323ec33d5a","name":"read","arguments":{"i":"Reading retirement enumeration behavior","path":"crates/spt-daemon/src/nethost.rs","selector":"1518-1602"}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":3494,"output":42,"cacheRead":88576,"cacheWrite":0,"totalTokens":92112,"cost":{"input":0.017470000000000003,"output":0.00126,"cacheRead":0.044288,"cacheWrite":0,"total":0.063018}},"stopReason":"toolUse","timestamp":1784247741473,"responseId":"resp_0ceecce325b7fe2d016a5975bfdecc819889cede907051f66d","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"fc_0ceecce325b7fe2d016a5975c11f2c8198a813d0323ec33d5a","type":"function_call","status":"completed","arguments":"{\"i\":\"Reading retirement enumeration behavior\",\"path\":\"crates/spt-daemon/src/nethost.rs\",\"selector\":\"1518-1602\"}","call_id":"call_nwdmstQmi4APhe4iWp6cAg6E","name":"read"}]},"duration":5092.42119999975,"ttft":3643.3837999999523,"contextSnapshot":{"promptTokens":92070,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_nwdmstQmi4APhe4iWp6cAg6E|fc_0ceecce325b7fe2d016a5975c11f2c8198a813d0323ec33d5a","toolName":"read","startedAt":"2026-07-17T00:22:26.612Z","args":{"path":"crates/spt-daemon/src/nethost.rs"},"intent":"Reading retirement enumeration behavior"},"id":"562ea0bd","parentId":"e02e3d71","timestamp":"2026-07-17T00:22:26.612Z"}
{"type":"message","id":"2007fada","parentId":"562ea0bd","timestamp":"2026-07-17T00:22:26.631Z","message":{"role":"toolResult","toolCallId":"call_nwdmstQmi4APhe4iWp6cAg6E|fc_0ceecce325b7fe2d016a5975c11f2c8198a813d0323ec33d5a","toolName":"read","content":[{"type":"text","text":"    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {\n…\n        self.bounded_block_on(async move {\n…\n            if !bytes.is_empty() {\n                send.write_all(&bytes).await?;\n            }\n            if finish {\n                send.finish().await?;\n            }\n            Ok(())\n        })\n    }\n\n    /// Every held stream's info — the query a re-attaching brain uses to find\n    /// streams (including peer-initiated ones that arrived while it was down).\n    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\n    /// enumeration is exactly what a future dispatcher generation would replay.\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\n        let streams = self.shared.streams.lock().unwrap();\n        streams\n            .iter()\n            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\n            .map(|(id, e)| {\n                let log = e.log.lock().unwrap();\n                NetStreamInfo {\n                    stream_id: *id,\n                    conn_id: e.conn_id,\n                    remote_id_hex: e.remote_id_hex.clone(),\n                    next_seq: log.next_seq,\n                    finished: log.finished,\n                    initiated_locally: e.initiated_locally,\n                    floor_seq: log.floor_seq(),\n                }\n            })\n            .collect()\n    }\n\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\n    /// serve loop ran to completion — its exchange is over, so no future\n    /// dispatcher generation may rediscover and replay it (the control-steal\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\n    /// reply sends when it removed the entry (see the `retired` field note).\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\n    /// call newly retired the row (idempotent; absent row = false).\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\n        let Some(entry) = self\n            .shared\n            .streams\n            .lock()\n            .unwrap()\n            .get(&stream_id)\n            .map(Arc::clone)\n        else {\n            return false;\n        };\n        let newly = !entry\n            .retired\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\n        if newly {\n            // A retired exchange's request bytes are dead weight — free the\n            // ring; the pinned opener and cursors stay (cheap, bounded).\n            entry.log.lock().unwrap().ring.clear();\n        }\n        newly\n    }\n\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\n    /// classification fact, independent of the evictable data ring. Outer\n    /// `None` = no such stream; inner `None` = no opener pinned (first `\\n`\n    /// not yet arrived, or capture gave up bounded).\n    // [impl->REQ-STREAM-OPENER-DURABLE]\n    pub fn stream_opener(&self, stream_id: u64) -> Option<Option<Vec<u8>>> {\n        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\n        // A retired row answers no-such-stream: classification exists only to\n        // serve, and a retired exchange must never be served again.\n        if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {\n            return None;\n        }\n        let line = entry.log.lock().unwrap().opener_line();\n        Some(line)\n    }\n\n    /// Mark a held stream **retentive** with backpressure `cap` chunks (M11-W3,\n    /// REQ-SHELL-4): its read log stops evicting (no byte loss) and its read pump\n    /// lags the sender once the undrained ring reaches `cap` (no unbounded growth).\n    /// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.\n    /// No-op if the stream is gone.\n    // [impl->REQ-SHELL-4]\n\n[Showing lines 1517-1605 of 2212. Use :1606 to continue]"}],"details":{"truncation":{"content":"            if !bytes.is_empty() {\r\n                send.write_all(&bytes).await?;\r\n            }\r\n            if finish {\r\n                send.finish().await?;\r\n            }\r\n            Ok(())\r\n        })\r\n    }\r\n\r\n    /// Every held stream's info — the query a re-attaching brain uses to find\r\n    /// streams (including peer-initiated ones that arrived while it was down).\r\n    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\r\n    /// enumeration is exactly what a future dispatcher generation would replay.\r\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\r\n        let streams = self.shared.streams.lock().unwrap();\r\n        streams\r\n            .iter()\r\n            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\r\n            .map(|(id, e)| {\r\n                let log = e.log.lock().unwrap();\r\n                NetStreamInfo {\r\n                    stream_id: *id,\r\n                    conn_id: e.conn_id,\r\n                    remote_id_hex: e.remote_id_hex.clone(),\r\n                    next_seq: log.next_seq,\r\n                    finished: log.finished,\r\n                    initiated_locally: e.initiated_locally,\r\n                    floor_seq: log.floor_seq(),\r\n                }\r\n            })\r\n            .collect()\r\n    }\r\n\r\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\r\n    /// serve loop ran to completion — its exchange is over, so no future\r\n    /// dispatcher generation may rediscover and replay it (the control-steal\r\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\r\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\r\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\r\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\r\n    /// reply sends when it removed the entry (see the `retired` field note).\r\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\r\n    /// call newly retired the row (idempotent; absent row = false).\r\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\r\n        let Some(entry) = self\r\n            .shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .get(&stream_id)\r\n            .map(Arc::clone)\r\n        else {\r\n            return false;\r\n        };\r\n        let newly = !entry\r\n            .retired\r\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\r\n        if newly {\r\n            // A retired exchange's request bytes are dead weight — free the\r\n            // ring; the pinned opener and cursors stay (cheap, bounded).\r\n            entry.log.lock().unwrap().ring.clear();\r\n        }\r\n        newly\r\n    }\r\n\r\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\r\n    /// classification fact, independent of the evictable data ring. Outer\r\n    /// `None` = no such stream; inner `None` = no opener pinned (first `\\n`\r\n    /// not yet arrived, or capture gave up bounded).\r\n    // [impl->REQ-STREAM-OPENER-DURABLE]\r\n    pub fn stream_opener(&self, stream_id: u64) -> Option<Option<Vec<u8>>> {\r\n        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\r\n        // A retired row answers no-such-stream: classification exists only to\r\n        // serve, and a retired exchange must never be served again.\r\n        if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {\r\n            return None;\r\n        }\r\n        let line = entry.log.lock().unwrap().opener_line();\r\n        Some(line)\r\n    }\r\n\r\n    /// Mark a held stream **retentive** with backpressure `cap` chunks (M11-W3,\r\n    /// REQ-SHELL-4): its read log stops evicting (no byte loss) and its read pump\r\n    /// lags the sender once the undrained ring reaches `cap` (no unbounded growth).\r\n    /// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.\r\n    /// No-op if the stream is gone.\r\n    // [impl->REQ-SHELL-4]\r","truncated":true,"truncatedBy":"lines","totalLines":696,"totalBytes":4051,"outputLines":89,"outputBytes":4051,"lastLinePartial":false,"firstLineExceedsLimit":false},"displayContent":{"text":"    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {\n…\n        self.bounded_block_on(async move {\n…\n            if !bytes.is_empty() {\r\n                send.write_all(&bytes).await?;\r\n            }\r\n            if finish {\r\n                send.finish().await?;\r\n            }\r\n            Ok(())\r\n        })\r\n    }\r\n\r\n    /// Every held stream's info — the query a re-attaching brain uses to find\r\n    /// streams (including peer-initiated ones that arrived while it was down).\r\n    /// Retired rows are hidden (ADR-0038 A): their exchange is over, and this\r\n    /// enumeration is exactly what a future dispatcher generation would replay.\r\n    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {\r\n        let streams = self.shared.streams.lock().unwrap();\r\n        streams\r\n            .iter()\r\n            .filter(|(_, e)| !e.retired.load(std::sync::atomic::Ordering::SeqCst))\r\n            .map(|(id, e)| {\r\n                let log = e.log.lock().unwrap();\r\n                NetStreamInfo {\r\n                    stream_id: *id,\r\n                    conn_id: e.conn_id,\r\n                    remote_id_hex: e.remote_id_hex.clone(),\r\n                    next_seq: log.next_seq,\r\n                    finished: log.finished,\r\n                    initiated_locally: e.initiated_locally,\r\n                    floor_seq: log.floor_seq(),\r\n                }\r\n            })\r\n            .collect()\r\n    }\r\n\r\n    /// Retire a stream row (ADR-0038 A): the dispatcher reports a stream whose\r\n    /// serve loop ran to completion — its exchange is over, so no future\r\n    /// dispatcher generation may rediscover and replay it (the control-steal\r\n    /// hazard). Retirement is ELIGIBILITY-VISIBILITY, not teardown: the row is\r\n    /// hidden from [`stream_infos`](Self::stream_infos) and the opener query\r\n    /// and its ring is freed, but the entry (send half, subscriber, pump) stays\r\n    /// alive so an in-flight reply always flushes — retire raced fire-and-forget\r\n    /// reply sends when it removed the entry (see the `retired` field note).\r\n    /// Physical teardown stays with the conn-close sweep. Returns whether this\r\n    /// call newly retired the row (idempotent; absent row = false).\r\n    // [impl->REQ-REDISPATCH-FINISHED-RETIRE]\r\n    pub fn retire_stream(&self, stream_id: u64) -> bool {\r\n        let Some(entry) = self\r\n            .shared\r\n            .streams\r\n            .lock()\r\n            .unwrap()\r\n            .get(&stream_id)\r\n            .map(Arc::clone)\r\n        else {\r\n            return false;\r\n        };\r\n        let newly = !entry\r\n            .retired\r\n            .swap(true, std::sync::atomic::Ordering::SeqCst);\r\n        if newly {\r\n            // A retired exchange's request bytes are dead weight — free the\r\n            // ring; the pinned opener and cursors stay (cheap, bounded).\r\n            entry.log.lock().unwrap().ring.clear();\r\n        }\r\n        newly\r\n    }\r\n\r\n    /// The stream's pinned opener line (ADR-0038 B) — the restart-durable\r\n    /// classification fact, independent of the evictable data ring. Outer\r\n    /// `None` = no such stream; inner `None` = no opener pinned (first `\\n`\r\n    /// not yet arrived, or capture gave up bounded).\r\n    // [impl->REQ-STREAM-OPENER-DURABLE]\r\n    pub fn stream_opener(&self, stream_id: u64) -> Option<Option<Vec<u8>>> {\r\n        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);\r\n        // A retired row answers no-such-stream: classification exists only to\r\n        // serve, and a retired exchange must never be served again.\r\n        if entry.retired.load(std::sync::atomic::Ordering::SeqCst) {\r\n            return None;\r\n        }\r\n        let line = entry.log.lock().unwrap().opener_line();\r\n        Some(line)\r\n    }\r\n\r\n    /// Mark a held stream **retentive** with backpressure `cap` chunks (M11-W3,\r\n    /// REQ-SHELL-4): its read log stops evicting (no byte loss) and its read pump\r\n    /// lags the sender once the undrained ring reaches `cap` (no unbounded growth).\r\n    /// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.\r\n    /// No-op if the stream is gone.\r\n    // [impl->REQ-SHELL-4]\r","startLine":1501,"lineNumbers":[1501,null,1515,null,1517,1518,1519,1520,1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531,1532,1533,1534,1535,1536,1537,1538,1539,1540,1541,1542,1543,1544,1545,1546,1547,1548,1549,1550,1551,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1563,1564,1565,1566,1567,1568,1569,1570,1571,1572,1573,1574,1575,1576,1577,1578,1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1599,1600,1601,1602,1603,1604,1605]},"meta":{"source":{"type":"path","value":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon\\src\\nethost.rs"},"truncation":{"direction":"head","truncatedBy":"lines","totalLines":2212,"totalBytes":4051,"outputLines":89,"outputBytes":4051,"shownRange":{"start":1517,"end":1605},"nextOffset":1606}}},"isError":false,"timestamp":1784247746631}}
{"type":"message","id":"eaf00cde","parentId":"2007fada","timestamp":"2026-07-17T00:22:31.834Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning D3 injection tests**"},{"type":"toolCall","id":"call_qFN60s2LtfinesQCZYxqfhwS|fc_0ceecce325b7fe2d016a5975c646688198b00c41acc283a2a3","name":"grep","arguments":{"i":"Checking retry integration stimulus","pattern":"take_injected_transient|SPT_DISPATCH_INJECT_TRANSIENT_ONCE|transient","path":"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests/redispatch.rs","selector":null,"case":true,"gitignore":true,"skip":null}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4533,"output":137,"cacheRead":88576,"cacheWrite":0,"totalTokens":93246,"reasoningTokens":51,"cost":{"input":0.022665,"output":0.00411,"cacheRead":0.044288,"cacheWrite":0,"total":0.071063}},"stopReason":"toolUse","timestamp":1784247746647,"responseId":"resp_0ceecce325b7fe2d016a5975c2db988198b0d171f4c58fda61","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5975c4c1e08198bf79f772c7b897b4","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXXGBdtIuu9ct4jmzjwkmbDQ1ba4PoJkJtTZxM4Zyx_MBe0t3RhyTrIb3sdjv7Du7tlaq2y23seAtbuhLNf6s88kZboV0cd92bmjN_9uDZoYefmelMzmycp3Fnl_SdutH8M7hUri4Yc57pUxlpHdT-hWP94ZbyEgGN5gE7Ad8Xcy4X4kEOzIm0nX28SPRbUkvHFHu2H_OK2qp_R17PARvrk33GHQ1VPblQMrQ1lpDGQ-_jsA2QJg94TA_X-46qeSNz261SAi2Orp7aL0gzciDnlTgTBU-efOyhayI9cpz0hbFfUn0qqgbFT5Hthf8EKUghp85fb6H-UOSCkl98Lctzt78Pfc46hhgJf6jGrg4KGBEULoinN8wgE3dJ_8pbSqde5whXprjO9IvsfKeck2A0sz2j5cUxXKVsf7TIoC-6LCIifn2jZcRBix3CUOohc2Fu1i2i-aJZMsfCalkY0Qqiua_wREGVYRy-ByzzbpwfgYTgTLiCoCatqhUc000YbAs3YMs6ty0GI3JSBz_3UzlTlDkg22yK5CNCID4rCK9qcdCn03MclP5omY2dBIuGiKf5Nlh-ks0uUgynMSw3EeND_Zfss55K_pIx3kZnh7cCQGLa7bjxJLAsMe9WupLOsfMff5d9VVTahKXD6UG0E2HrhJoYUm_q_WP6_wX1tZ1iO50Y4HEbO2rLmnLluPSm3kW0QPF4FFWwjsKcHNp8rvrx4U89Q94NL4MyVVxMv1n19mMJfroMX_ZYTI4y-P52jAfsFBnMbDYEpPKBmZ2RAPg1LivbV2h5tt7thGctXYa7JAO4U2lIDzbuWZ7lm1622Jwa3G3JQ6C3p_fkAPT3jQg3HHoyA3YG_V8XcsQlYQUMc_eKtZMcNU3bNmcSda3pnuJ7X5qvDoPWOMaeTcaXmPMGa0deVLnh3MZAJKFqkzg9gHfrinmVcHJrdl32Y8JKYpRNoepQXjDfJVcq2mSssuU9h2sgExD3Nworo_lo7DYU0p9RhSHPVXW5wtUmRccz6yTd0P1L8LDXvoMeBW1lOvY2_C1qPHs3WdwarfvsR21RhrF-jeeATjZ1AW3MzPd1ePn1D5PfCFcUZ6OJulAvYkzE3VDcy3IBiHxgjhfMZ03NucsUHMFo_hrW2MuenI8Zig2ddE3XeHzECC-rMd3miMl0pSwP3zLArY2NOrAAxOvk1PV16yt4cHP6JMyxExB_e8ulwSQg_dCLnqLMZND359r6skU54nAss7EEAsTzOcmb_oM7U=","summary":[{"type":"summary_text","text":"**Planning D3 injection tests**"}]},{"id":"fc_0ceecce325b7fe2d016a5975c646688198b00c41acc283a2a3","type":"function_call","status":"completed","arguments":"{\"i\":\"Checking retry integration stimulus\",\"pattern\":\"take_injected_transient|SPT_DISPATCH_INJECT_TRANSIENT_ONCE|transient\",\"path\":\"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests/redispatch.rs\",\"selector\":null,\"case\":true,\"gitignore\":true,\"skip\":null}","call_id":"call_qFN60s2LtfinesQCZYxqfhwS","name":"grep"}]},"duration":5185.885899998248,"ttft":2101.038599997759,"contextSnapshot":{"promptTokens":93109,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_qFN60s2LtfinesQCZYxqfhwS|fc_0ceecce325b7fe2d016a5975c646688198b00c41acc283a2a3","toolName":"grep","startedAt":"2026-07-17T00:22:31.834Z","args":{"path":"crates/spt-daemon/src/dispatch.rs; crates/spt-daemon/tests/redispatch.rs"},"intent":"Checking retry integration stimulus"},"id":"5688eb34","parentId":"eaf00cde","timestamp":"2026-07-17T00:22:31.834Z"}
{"type":"message","id":"c1920034","parentId":"5688eb34","timestamp":"2026-07-17T00:22:31.867Z","message":{"role":"toolResult","toolCallId":"call_qFN60s2LtfinesQCZYxqfhwS|fc_0ceecce325b7fe2d016a5975c646688198b00c41acc283a2a3","toolName":"grep","content":[{"type":"text","text":"# crates/spt-daemon/\n\n## src/\n### dispatch.rs\n 170|/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n*171|/// insert performed BEFORE worker spawn and never cleared — one transient\n 172|/// worker-setup failure permanently abandoned the stream. Claims now\n*173|/// distinguish in-flight vs terminal vs retryable, and a transient failure\n 174|/// requeues bounded (backoff-shaped, never a hot loop).\n 175|#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 176|enum ClaimState {\n...\n 181|    Terminal,\n*182|    /// A transient failure released the claim; eligible again at `next_at`.\n 183|    Retry { attempts: u32, next_at: Instant },\n 184|}\n 185|\n*186|/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n 187|/// failures recover, a persistently-failing stream goes terminal loud.\n 188|const MAX_DISPATCH_ATTEMPTS: u32 = 3;\n 189|\n...\n 256|\n*257|/// TEST-RIG-ONLY (the D3 regression): `SPT_DISPATCH_INJECT_TRANSIENT_ONCE`\n 258|/// set at dispatcher start makes exactly ONE worker fail before setup —\n*259|/// proving a transient worker-start failure requeues and recovers through\n 260|/// the REAL claim path. Never set in production; the latch arms once per\n 261|/// process and disarms on first take.\n*262|fn take_injected_transient() -> bool {\n 263|    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\n 264|    let armed = ARMED.get_or_init(|| {\n*265|        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\").is_ok())\n 266|    });\n 267|    armed.swap(false, Ordering::SeqCst)\n 268|}\n...\n 308|    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n*309|    // abandoned a stream on the first transient failure.\n 310|    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n 311|\n 312|    while !stop.load(Ordering::Relaxed) {\n...\n 391|/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n*392|/// (terminal); `Err` = transient (the claim requeues bounded).\n 393|fn first_line(\n 394|    broker_name: &str,\n 395|    stream_id: u64,\n...\n 403|        // newline still in flight, or the capture gave up bounded). The\n*404|        // next_seq guard proved bytes exist, so treat as transient — the\n 405|        // bounded claim retry re-reads it shortly, and a stream that never\n 406|        // pins goes terminal when the budget is spent (no hot loop).\n 407|        Ok(None) => {\n...\n 466|) -> DispatchOutcome {\n*467|    if take_injected_transient() {\n 468|        return DispatchOutcome::Failed(\n*469|            \"injected transient worker-start failure (test valve)\".into(),\n 470|        );\n 471|    }\n 472|    let line = match first_line(broker_name, stream_id, floor_seq) {\n...\n 966|    #[test]\n*967|    fn transient_failure_requeues_bounded_and_terminal_never_reenters() {\n 968|        let now = Instant::now();\n 969|\n 970|        // Fresh row → claimable with zero prior failures.\n\n## tests/\n### redispatch.rs\n 17|//!   resumes the SAME operator stream.\n*18|//! - D3: ONE injected transient worker-start failure → the claim requeues\n 19|//!   and the retry serves the stream (no permanent abandonment, no\n 20|//!   duplicate controller).\n 21|//!\n...\n 570|\n*571|// [int->REQ-DISPATCH-CLAIM-RETRY] D3 — one injected transient worker-start\n*572|// failure (the SPT_DISPATCH_INJECT_TRANSIENT_ONCE test valve, armed on the\n 573|// generation's env): the FIRST worker attempt fails before setup, the claim\n 574|// requeues (bounded backoff), and the retry serves the stream — the operator\n 575|// still gets the viewport, exactly one controller. Pre-fix the one-shot\n...\n 577|#[test]\n*578|fn transient_worker_start_failure_recovers_via_claim_retry() {\n 579|    init_home();\n 580|    let dir = tempfile::tempdir().expect(\"tempdir\");\n 581|    let (name_a, name_b) = (unique_name(), unique_name());\n...\n 593|        dir.path().join(\"gen1.log\"),\n*594|        &[(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\", \"1\")],\n 595|    );\n 596|\n 597|    let mut operator = connect_retry(&name_b);\n...\n 613|    assert!(\n*614|        log.contains(\"injected transient worker-start failure\"),\n*615|        \"the transient-failure valve never fired — the retry path was not exercised: {log}\"\n 616|    );\n 617|\n 618|    gen1.kill();"}],"details":{"scopePath":"crates/spt-daemon/src/dispatch.rs, crates/spt-daemon/tests/redispatch.rs","searchPath":"C:\\Users\\decid\\Documents\\projects\\spt-core\\crates\\spt-daemon","cwd":"C:\\Users\\decid\\Documents\\projects\\spt-core","matchCount":21,"fileCount":2,"files":["crates/spt-daemon/src/dispatch.rs","crates/spt-daemon/tests/redispatch.rs"],"fileMatches":[{"path":"crates/spt-daemon/src/dispatch.rs","count":14},{"path":"crates/spt-daemon/tests/redispatch.rs","count":7}],"truncated":false,"displayContent":"# crates/spt-daemon/\n\n## src/\n### dispatch.rs\n 170│/// REQ-DISPATCH-CLAIM-RETRY). The pre-fix `claimed` was a one-shot `HashSet`\n*171│/// insert performed BEFORE worker spawn and never cleared — one transient\n 172│/// worker-setup failure permanently abandoned the stream. Claims now\n*173│/// distinguish in-flight vs terminal vs retryable, and a transient failure\n 174│/// requeues bounded (backoff-shaped, never a hot loop).\n 175│#[derive(Debug, Clone, Copy, PartialEq, Eq)]\n 176│enum ClaimState {\n    │...\n 181│    Terminal,\n*182│    /// A transient failure released the claim; eligible again at `next_at`.\n 183│    Retry { attempts: u32, next_at: Instant },\n 184│}\n 185│\n*186│/// Bounded retry budget per stream per dispatcher lifetime — transient setup\n 187│/// failures recover, a persistently-failing stream goes terminal loud.\n 188│const MAX_DISPATCH_ATTEMPTS: u32 = 3;\n 189│\n    │...\n 256│\n*257│/// TEST-RIG-ONLY (the D3 regression): `SPT_DISPATCH_INJECT_TRANSIENT_ONCE`\n 258│/// set at dispatcher start makes exactly ONE worker fail before setup —\n*259│/// proving a transient worker-start failure requeues and recovers through\n 260│/// the REAL claim path. Never set in production; the latch arms once per\n 261│/// process and disarms on first take.\n*262│fn take_injected_transient() -> bool {\n 263│    static ARMED: OnceLock<AtomicBool> = OnceLock::new();\n 264│    let armed = ARMED.get_or_init(|| {\n*265│        AtomicBool::new(std::env::var(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\").is_ok())\n 266│    });\n 267│    armed.swap(false, Ordering::SeqCst)\n 268│}\n    │...\n 308│    // replaces the pre-fix one-shot `HashSet` whose burned claims permanently\n*309│    // abandoned a stream on the first transient failure.\n 310│    let claims: Arc<Mutex<HashMap<u64, ClaimState>>> = Arc::new(Mutex::new(HashMap::new()));\n 311│\n 312│    while !stop.load(Ordering::Relaxed) {\n    │...\n 391│/// broker predates the verb (KH-2.3 N-1 window). `Ok(None)` = unclassifiable\n*392│/// (terminal); `Err` = transient (the claim requeues bounded).\n 393│fn first_line(\n 394│    broker_name: &str,\n 395│    stream_id: u64,\n    │...\n 403│        // newline still in flight, or the capture gave up bounded). The\n*404│        // next_seq guard proved bytes exist, so treat as transient — the\n 405│        // bounded claim retry re-reads it shortly, and a stream that never\n 406│        // pins goes terminal when the budget is spent (no hot loop).\n 407│        Ok(None) => {\n    │...\n 466│) -> DispatchOutcome {\n*467│    if take_injected_transient() {\n 468│        return DispatchOutcome::Failed(\n*469│            \"injected transient worker-start failure (test valve)\".into(),\n 470│        );\n 471│    }\n 472│    let line = match first_line(broker_name, stream_id, floor_seq) {\n    │...\n 966│    #[test]\n*967│    fn transient_failure_requeues_bounded_and_terminal_never_reenters() {\n 968│        let now = Instant::now();\n 969│\n 970│        // Fresh row → claimable with zero prior failures.\n\n## tests/\n### redispatch.rs\n  17│//!   resumes the SAME operator stream.\n *18│//! - D3: ONE injected transient worker-start failure → the claim requeues\n  19│//!   and the retry serves the stream (no permanent abandonment, no\n  20│//!   duplicate controller).\n  21│//!\n    │...\n 570│\n*571│// [int->REQ-DISPATCH-CLAIM-RETRY] D3 — one injected transient worker-start\n*572│// failure (the SPT_DISPATCH_INJECT_TRANSIENT_ONCE test valve, armed on the\n 573│// generation's env): the FIRST worker attempt fails before setup, the claim\n 574│// requeues (bounded backoff), and the retry serves the stream — the operator\n 575│// still gets the viewport, exactly one controller. Pre-fix the one-shot\n    │...\n 577│#[test]\n*578│fn transient_worker_start_failure_recovers_via_claim_retry() {\n 579│    init_home();\n 580│    let dir = tempfile::tempdir().expect(\"tempdir\");\n 581│    let (name_a, name_b) = (unique_name(), unique_name());\n    │...\n 593│        dir.path().join(\"gen1.log\"),\n*594│        &[(\"SPT_DISPATCH_INJECT_TRANSIENT_ONCE\", \"1\")],\n 595│    );\n 596│\n 597│    let mut operator = connect_retry(&name_b);\n    │...\n 613│    assert!(\n*614│        log.contains(\"injected transient worker-start failure\"),\n*615│        \"the transient-failure valve never fired — the retry path was not exercised: {log}\"\n 616│    );\n 617│\n 618│    gen1.kill();"},"isError":false,"timestamp":1784247751866}}
{"type":"message","id":"b5721876","parentId":"c1920034","timestamp":"2026-07-17T00:24:11.468Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"**Planning detailed schema and inference labeling**\n\n**Emphasizing direct causes with verbose yield**"},{"type":"toolCall","id":"call_Cxv1BnOwpBApkWbzuozfrjun|fc_0ceecce325b7fe2d016a5975ccb17481989d542837847ed3da","name":"yield","arguments":{"type":"result","result":{"data":{"summary":"## Release identity and intended v0.34 contract\n\n- `v0.33.0` is tag object/ref `21a46d5…`, with release commit `42d06a8…` (confirmed independently by the regression-archeology peer). The v0.34 W1 implementation then landed as:\n  - `ad7fde8acb97b5c839316121d341375b077092df` — retirement, durable opener, retryable claims;\n  - `2a110eed6f97d3dab96ed301d1b6d1df9a2cc002` — finished Attach is terminal/unserved, while request/reply protocols whose read side is FIN remain servable;\n  - `84253572cd5e0cef57201ba671a8c201c6dbbdd8` — retirement is eligibility/visibility only, never physical stream teardown;\n  - `571d6a55e9949ce1b96aef1ba00f5e007a0a9521` — ADR-0038 records that ruling.\n- The release branch commit is `eb9fd8643921930ce21cc7ef0751a27360fc14bc` (`release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1`); the published v0.34 tag target is `b7b074c2509c1015fe341185662b4622d661f274`.\n- ADR-0038 and requirements `REQ-HAZARD-REDISPATCH-CONTROL-STEAL`, `REQ-REDISPATCH-FINISHED-RETIRE`, `REQ-STREAM-OPENER-DURABLE`, and `REQ-DISPATCH-CLAIM-RETRY` define the intended behavior: a fresh brain must not replay terminal historical Attach streams; an active stream remains classifiable after its 4,096-chunk ring rolls; transient worker setup failures retry; legitimate same-operator active Attach reconstruction remains allowed.\n- `CHANGELOG.md` promises that `spt update`, `spt update --restart`, and `spt daemon refresh` no longer disturb an attached remote session. That promise is accurate for the intended brain-only redispatch fix, but overstates `--restart`: `cli.rs` calls its final `Finish` leg “LETHAL,” explicitly saying it performs a full daemon restart and kills an invoking hosted PTY. `rc.rs` masks an expected broker sever with a visible, bounded 30-second reconnect loop. A silent frozen viewport is therefore not the intended `--restart` UX; a 30-second countdown banner is.\n- The v0.34 release also shipped with malformed release-note metadata: the v0.34 heading consumed the preceding `## [0.33.0]` heading, so the workflow’s section extractor could publish both releases as one body. Commit `d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7` restored the heading after the tag. `release.yml` only checks that the requested section is nonempty; it does not validate a contiguous version ladder. The runbook now records this exact v0.34 incident and recommends a heading-ladder check.\n\n## What changed that can amplify a 20–30 second stall\n\nThe v0.34 W1 change adds no 20–30 second timer. Its dispatcher cadence is 100 ms; retries are at 500 ms and 1 second, with at most three attempts. The exact 15-second physical write deadline and 30-second RC reconnect window both predate the v0.34 delta.\n\nThe field trace supplied by Main materially changes the diagnosis:\n\n- 33 `CONN_WRITE_POISONED` incidents are attributed to `stream-subscriber` connections.\n- Every poisoned write reaches the configured bound at approximately 15,000 ms.\n- The corresponding `DISPATCH` failure is logged 15.1–62 seconds after write start; recent examples are 17.6, 25.4, 29.1, 30.1, 31.1, 36.6, 59.4, and 61.6 seconds.\n- The cold generation has 4,361 `stream-sub-attach` records and 584 10-second `PUMP_PEER_FAIL` events.\n- Stream 4834’s write began 37.354 seconds after the recent webbie controller attach and poisoned at +52.366 seconds.\n\nThis is an exact source match for the first 15 seconds and a plausible v0.34 amplification for the remainder:\n\n1. `run_dispatch_loop` enumerates every eligible broker stream every 100 ms and immediately calls `thread::spawn` once per newly claimed row. There is no semaphore, worker pool, queue bound, batch cap, or backpressure. A 4,361-row cold enumeration can therefore create thousands of simultaneous OS threads/connections.\n2. v0.34’s durable classification performs a fresh opener RPC connection per row. `first_line` falls back to the old subscribe/replay path on **any** opener RPC error, not only an old broker’s unknown-verb response. The family worker then opens another broker connection and subscribes to the actual stream.\n3. `serve_attach` sends `net-stream-subscribe`, then—when it has the pinned Request—immediately sends the PTY-session `attach_as` command before it begins its event-read loop. On the broker, `StreamLog::attach` installs the subscriber and synchronously writes every retained chunk while holding that stream’s `Mutex<StreamLog>`. Under a thousands-thread cold-start burst, the client worker can be descheduled or otherwise fail to drain the named pipe promptly; the broker’s Windows `WriteFileEx` fills/blocks.\n4. `BrokerConn` enforces the 15-second absolute write deadline. `CONN_WRITE_POISONED` is emitted when the watchdog marks the operation timed out and requests `CancelIoEx`; it does **not** mean the write completed or the physical connection has severed.\n5. After logging poison, the watchdog waits in 100 ms slices while the original `write_frame` remains in flight, repeatedly calling `CancelIoEx`. It invokes `DisconnectNamedPipe` only after `write_frame` reports completion. Meanwhile, the dispatcher’s ordinary `BrainConn::Whole` uses a direct blocking `read_frame` with no read deadline. The worker cannot return `DispatchOutcome::Failed`, and thus cannot emit the `DISPATCH` failure or release its claim, until the server connection is actually severed/EOFs.\n6. [INFERENCE] The field’s variable 0.1–47 second post-poison tail is therefore explained by delayed Windows cancellation completion plus severe scheduler pressure from the unbounded worker burst. It is not a second intentional timeout. This is falsifiable by adding lifecycle stamps for `cancel-issued`, `write-returned/inflight-cleared`, `sever`, client EOF, and worker outcome; the long interval should occur between poison/cancel and inflight-clear/sever.\n7. v0.33’s one-shot claim abandoned a stream after this failure. v0.34 changes every `Failed` result into a retry after 500 ms, then 1 second, up to three total attempts. Each attempt can install a fresh subscriber and synchronously replay the ring again. Thus v0.34 can turn one permanent loss into recurrent 15-second transport wedges: one deadline plus cancellation lag naturally gives 20–30 seconds; two near-minimal poisoned attempts plus the 500 ms backoff gives approximately 30.5 seconds; three attempts can approach or exceed 45 seconds. This is the strongest source-grounded v0.33→v0.34 regression relation.\n8. Retirement does not break the loop on failure: only `DispatchOutcome::Served` triggers `net_stream_retire`. A poisoned worker returns `Failed`, so its row remains eligible for the new retry policy. Across future brain generations, the per-process claim map is empty again.\n\n## Causal tie to PTY/RC stalls, and its limits\n\n- [INFERENCE, strong if stream identity correlates] A poisoned target-side Attach subscriber directly stalls the wire→session leg: operator input/resize records cannot reach the PTY worker while synchronous replay owns that stream log and the worker connection is wedged. The terminal therefore feels frozen even though the ConPTY child and broker-held PTY output log are healthy. Stream 4834 should be decoded/classified from its pinned opener and matched to the webbie Attach; if it is that active controller’s Attach stream, the +37.354s start/+52.366s poison is direct incident evidence, not merely timing similarity.\n- The stream lock is per QUIC stream, not the global PTY/session lock. The immediate mechanism does **not** by itself stall the ConPTY child, the broker’s session `OutputLog`, or every broker IPC connection. It predicts an affected remote attach/input stream, with unrelated PTYs and fresh control-plane probes remaining live.\n- [INFERENCE] Thousands of simultaneous dispatcher workers/connections can add process-wide scheduler and IPC pressure, so several of approximately five active PTYs may appear to stall together. That wider effect must be measured; the lock topology alone does not prove a broker-wide stall.\n- The separate 30-second `rc` reconnect path begins only after the viewport sees a reconnectable EOF/sever and should paint a countdown banner. During the poison→sever delay, `rc` sees no EOF and can remain silently frozen. A trace showing a silent freeze first and a countdown only after `sever` would distinguish these phases.\n- A 10-second `PUMP_PEER_FAIL` is peer-pump failure timing, not sufficient evidence of a PTY stall. The reported 584 events indicate substantial concurrent network churn/load, but the exact causal seam for the 15-second incidents is the attributed stream-subscriber writer.\n- A finished Attach should be classified from its pinned opener, stopped by `finished_row_is_terminal`, and retired **before** `serve_attach` subscribes. Therefore poison stream IDs that all decode to already-finished Attach rows would falsify the intended v0.34 gate or demonstrate stale/mixed broker semantics. Conversely, active Attach or other request/reply families are expected to subscribe and remain exposed.\n\n## Compatibility relation\n\n- The W1 wire changes are additive, but a v0.34 brain on an older broker cannot use durable opener or retirement verbs. `first_line` falls back to ring replay; retirement is best-effort and silently ignored until the broker cycles. The existing N-1 test checks only handshake/heartbeat steady state and does not create stream rows or exercise either new verb.\n- This mixed-image gap contradicts the broad changelog promise for ordinary brain-only apply: the new brain can run, but the old broker cannot fully deliver v0.34 lifecycle truth. It is not the explanation for the supplied field trace if broker and brain are both confirmed v0.34, but it remains a release-contract hole.\n- Adding broker verbs does not change the declared IPC version/resource ABI, so the update classifier can label the release `BrainOnly`; the project already exposes `broker_image`/`broker_stale` precisely because installed brain and running broker may differ. A broker refresh/full restart activates the new verbs but invokes the separate reconnect/restart tradeoff.\n\n## Missing coverage that allowed this regression class\n\n1. `redispatch.rs` D1/D1b/D2/D3 tests correctness for a handful of streams. D3 injects one failure **before setup**, so it proves claim-state arithmetic but never exercises a failed/poisoned subscriber, synchronous replay, cancellation lag, or repeated reattachment.\n2. There is no dispatcher concurrency/burst test with thousands of retained rows and five or more simultaneous active remote Attach/PTYS. Existing `brain_resume_conn_deadlock.rs` uses six flood PTYs and checks broker heartbeat liveness, not dispatcher cold rediscovery, RC viewport gaps, input latency, or stream-subscriber poison.\n3. `conn_blackhole_lifecycle.rs` and `brain_decouple.rs` cover controller/session subscribers, not `NetHost::StreamLog` subscribers driven through `run_dispatch_loop`. They shrink the production 15-second bound to 400 ms and assert eventual retirement/probe health, not the interval from `CONN_WRITE_POISONED` to `write_frame` completion/sever/client EOF.\n4. `daemon_refresh_e2e.rs` proves a local hosted child PID, broker PID, and basic functionality survive refresh. It does not keep five real cross-node RC Attach streams under continuous input/output and assert maximum visible gap through a fresh dispatcher generation.\n5. `n1_pairing.rs` proves new-brain/old-broker handshake and heartbeat only; it has zero stream rows and no opener/retire call. It cannot detect the mixed-image v0.34 semantic gap.\n6. No release check validates consecutive changelog headings, which is why the v0.33 heading disappeared in the tagged v0.34 notes.\n\n## Focused fix and test design\n\nPriority fix design:\n\n1. Bound redispatch concurrency at the source. Replace per-row unbounded `thread::spawn` with a small fixed worker pool/semaphore and a bounded pending queue; batch enumeration so one cold 4,361-row table cannot instantiate 4,361 connections/threads in one sweep. Preserve one claim state per stream and never hold the claims mutex during worker I/O.\n2. Remove synchronous socket I/O from `StreamLog::attach` while preserving replay-before-live ordering. The boring safe pattern is a per-subscriber dedicated writer/queue, analogous to the existing viewer writer: install the subscriber and enqueue the retained replay under the stream lock, release the lock, and let the writer perform framed I/O. Live append only enqueues nonblocking. Bound the queue and retain cursor/ring fallback so overload drops to resume semantics rather than blocking the QUIC read pump.\n3. Make retry classification narrower. A pre-setup transient connect/opener race may retry; a deadline-poisoned subscriber/replay under system overload should not immediately repeat the same expensive replay blindly. At minimum, use a much longer/circuit-broken backoff for `TimedOut` and cap global retries. Do not treat every I/O failure identically.\n4. Tighten opener fallback: fall back to ring peek only for the explicit old-broker unknown-verb response. A timeout/poison/transport error should return `Failed` under the bounded scheduler, not open another subscription/replay connection.\n5. Instrument the Windows poison lifecycle before changing teardown safety: log CancelIoEx return/GetLastError, inflight-clear, `DisconnectNamedPipe`, handler cleanup, client EOF, dispatch family/attempt, and active/pending worker gauges. If cancel-to-completion remains long, revise the Windows abort primitive so peer read closure is prompt without releasing a frame buffer while its overlapped write is still live; the present code intentionally waits for that buffer-release handshake, so immediate sever must be designed, not guessed.\n6. Correct the release contract: separate brain-only refresh/update survival from `--restart`’s lethal full cycle and visible RC reconnect; either require broker feature activation for W1 or state the old-broker degradation explicitly.\n\nFocused regression tests:\n\n- Windows integration test: create at least five active remote Attach sessions plus thousands of retained stale/nonterminal streams, start a fresh production dispatcher, drive unique input and autonomous output markers continuously, and assert a bounded maximum marker gap, no `CONN_WRITE_POISONED`, bounded worker/handler counts, and independent `sessions`/`net-status` probe latency.\n- Poison lifecycle test through **NetHost stream subscription**, not session controller subscription: black-hole a stream-subscriber connection, exercise real dispatcher retry, and assert `poison → inflight-clear → sever → client EOF → DispatchOutcome` within a tight post-deadline bound. Keep a shortened-deadline structural CI variant and add a Windows release/HITL run using the real 15-second bound.\n- Retry test: inject failure after a subscriber is installed or after its replay blocks. Assert one stream cannot produce repeated 15-second wedges and that retry does not exceed the global concurrency budget.\n- Refresh E2E: cycle the brain while five remote RC streams remain active; assert child PID and broker output seq continue, operator input echoes, viewports continue, no old output retypes, no control steal, and no reconnect banner for a brain-only cycle.\n- Mixed-image E2E: v0.34 brain against v0.33 broker with actual active/finished streams; explicitly assert the documented degraded behavior or require broker activation. Handshake-only is insufficient.\n- Release lint: parse all `## [x.y.z]` headings and reject missing/duplicate/non-descending neighbors before notes extraction.\n\n## Fast falsification matrix for Doyle\n\n- Broker session output sequence advances during the visible freeze, child PID unchanged, fresh broker probes answer, and affected Attach input does not echo: local ConPTY is healthy; the per-stream subscriber/redispatch path is causal.\n- Broker session output sequence itself stops while child should be producing, but IPC probes answer: investigate that session’s ConPTY/output drain, not RC transport.\n- Independent fresh `sessions`/`net-status` probes also block: broker-wide IPC/scheduler pressure is present; capture dispatcher worker/handler counts and thread scheduling.\n- `CONN_WRITE_POISONED` stream ID is the affected active Attach and poison/cancel precedes recovery: causal tie confirmed.\n- Poison IDs are unrelated protocol families/peers and affected Attach stream has uninterrupted data: timing correlation is incidental.\n- Long post-poison interval is between cancel and inflight-clear/sever: current watchdog mechanism confirmed. If sever occurs promptly but `DISPATCH` remains late, the delay is instead client scheduling/logging after EOF.","files":[{"path":"CHANGELOG.md","description":"v0.34 user-facing promise at lines 49–56: update/refresh, including `--restart`, should not disturb an attached remote session; current file also shows restored separate v0.33 heading."},{"path":".git/logs/HEAD","description":"Exact v0.34 implementation/release history: W1 commits at lines 1458–1461, release branch commit at 1465, tag-line integration at 1467, and post-tag v0.33-heading repair at 1469."},{"path":".git/logs/refs/heads/release/v0.34.0","description":"Release branch creation and exact counter-61 release commit `eb9fd864…`."},{"path":"docs/adr/0038-broker-stream-lifecycle-truth.md","description":"Canonical v0.34 design: terminal retirement, pinned opener, retryable claims, unchanged active reconstruction, and eligibility-only retirement consequences."},{"path":"docs/REDISPATCH-TRUTH-TRIAGE.md","description":"Field motivation (4/5 endpoints frozen twice), intended W1 scope, v0.33-boundary dispatch, and acceptance gates."},{"path":"traceable-reqs.toml","description":"Requirements at lines 2433–2450 for controller-steal prevention, retirement, durable opener, and retry claims; integration evidence descriptions reveal D3 is only a pre-setup synthetic transient."},{"path":"crates/spt-daemon/src/dispatch.rs","description":"Core regression seam: 100 ms poll, 500 ms/1 s retry with max three attempts (lines 77–230); unbounded per-row thread spawning (299–361); opener-on-new-connection with broad error fallback to replay subscription (391–452); Attach terminal gate and serve path (479 onward)."},{"path":"crates/spt-daemon/src/nethost.rs","description":"`StreamLog::append/finish/attach` performs subscriber writes synchronously under the per-stream log lock (roughly 284–351); subscribe calls `log.lock().attach` (1676–1693); stream enumeration excludes retired rows and retirement only clears eligibility/ring (1518–1599)."},{"path":"crates/spt-daemon/src/conn.rs","description":"Windows bounded connection writer: 15-second deadline ownership, poison/cancel, 100 ms recancel loop, and sever only after inflight write completion (roughly 179–399 and 459–604). Explains why poison log precedes dispatch failure by a variable interval."},{"path":"crates/spt-daemon/src/brain.rs","description":"Ordinary `BrainConn::Whole` clients do direct blocking reads with no deadline; only pump Split connections use channel deadlines (199–333 and 1734–1778). Dispatcher worker therefore awaits actual sever/EOF after poison."},{"path":"crates/spt-daemon/src/attach.rs","description":"Target-side Attach flow: net-stream subscription, second connection for output forwarding, pinned Request handling, then session attach before the event read loop (279–443). Establishes the stream-subscriber/input-leg causal boundary."},{"path":"crates/spt-daemon/src/broker.rs","description":"Each broker client gets one bounded `BrokerConn`; `handle_conn` synchronously dispatches `KIND_NET_STREAM_SUBSCRIBE` and cleans stream subscriptions only after receive EOF (roughly 2980–3230)."},{"path":"crates/spt/src/cli.rs","description":"Update planning explicitly defines `Finish`/`--restart` as lethal full-daemon restart that kills an invoking hosted PTY, while ordinary FetchApply is brain-only (5038–5098)."},{"path":"crates/spt/src/rc.rs","description":"Visible RC reconnect loop and exact 30-second window with one-second pauses (1545–1665); distinguishes intended broker-bounce UX from silent pre-sever poison stall."},{"path":"crates/spt-daemon/tests/redispatch.rs","description":"W1 D1/D1b/D2/D3 integration coverage; D3 injects a single transient before setup, so it never covers subscriber replay poison or recurring retry stalls."},{"path":"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","description":"Six-session flood coverage checks heartbeat liveness, not cold dispatcher fan-out, RC input/output gaps, or stream-subscriber timeout behavior."},{"path":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","description":"Windows blackhole coverage for session controller connections with shortened deadline; not a NetHost stream subscriber through the dispatcher."},{"path":"crates/spt/tests/daemon_refresh_e2e.rs","description":"Proves local broker/PTY child survival through a brain cycle but has no five-way remote Attach/RC continuity assertion."},{"path":"crates/spt/tests/n1_pairing.rs","description":"New-brain/old-broker N-1 test covers handshake and heartbeat only; no stream opener, retirement, replay, or active session row."},{"path":".github/workflows/release.yml","description":"Extracts requested CHANGELOG section and only rejects empty notes; no version-ladder/neighbor validation, allowing the v0.34/v0.33 heading accident."},{"path":"docs/RELEASE-RUNBOOK.md","description":"Records that v0.34 consumed the v0.33 heading and recommends a contiguous heading grep; currently a manual self-check/candidate CI lint."}],"architecture":"A remote controller opens a QUIC Attach stream. The stable broker owns the QUIC stream table and ConPTY/session handles; the replaceable brain’s dispatcher enumerates peer-initiated rows and creates a worker per claim. A worker classifies from the broker-pinned opener, subscribes a local broker IPC connection to the Attach stream, then attaches that same brain connection to the broker-held PTY session; a second broker connection forwards session output back onto QUIC. In v0.34, claim failures retry up to three times and successful exchanges retire from future enumeration. The critical pressure path is `fresh dispatcher → unbounded workers/connections → synchronous StreamLog replay under per-stream lock → Windows named-pipe write blocks → 15s BrokerConn poison/cancel → delayed inflight completion/sever → blocking Whole-brain read finally EOFs → DispatchOutcome::Failed → v0.34 retry`. This is a local RC/stream transport stall when confined to one Attach stream, not a ConPTY output stall. It becomes a broader IPC/scheduler stall only through the thousands-worker fan-out. A full broker restart is a separate path: it deliberately kills hosted PTYs and `rc` uses a visible 30-second reconnect window; a brain-only refresh is supposed to preserve broker, children, streams, and viewport continuously."}}}}],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":4840,"output":5386,"cacheRead":89600,"cacheWrite":0,"totalTokens":99826,"reasoningTokens":162,"cost":{"input":0.024200000000000003,"output":0.16158,"cacheRead":0.0448,"cacheWrite":0,"total":0.23058}},"stopReason":"toolUse","timestamp":1784247751871,"responseId":"resp_0ceecce325b7fe2d016a5975c83c7c8198968e86e3e1deda73","providerPayload":{"type":"openaiResponsesHistory","provider":"openai-codex","dt":true,"items":[{"id":"rs_0ceecce325b7fe2d016a5975c9397881989af4cd42a0e98f2e","type":"reasoning","content":[],"encrypted_content":"gAAAAABqWXXM-WnYz7EowvSk7F2iJTUAhNQ5uN9wfZcoNtjiv6-bVK2Ih8CYo-OAFFgmQESRLV9DRHCH8kjLXKQWF7fnqrWQxUqbhV2HhklCHcR50Y-c1ng8HzIAGuZlTJcqUciCQHf51EI5RTmgfhh9UNOqyniWo_2i0XX5h34isCpxBAuERjGcjIn8U5JOlMPtDIWJ6a5uems8Ey5xApXHVRllQOFve60loZ0NiJbHHl5vIR6D7WObhqQsiSJQw14c6ujJnVN49fyJqPVJlERGzIuyBlC9W53xCy4uuQ1xp7WbgQa1yrWt_Xu_oyVUnoqppZ7-9ML_Gsm3DMbj3vrXRZsV4RkGc6gaE62EayEgjAXesN0CRnfuQwD4x7Q_CQ2iiWkin592__x89NfV42i0mc8VcDNULGM9dpmfJ6aEfxz1XxHGVCehrqyVNq6piDp4_gtvN1RbnsT0De9QUgj0HcQgj0fSDbvFMkyK2CTae8wHcAQzkKiKLLxXR1RTmsgRoNsL-VBy2XVVjcOZ30gcCKQ5WUHRzRB0GWw3-y10DFIZmPSm5QwQLEtzXAw6QZp4buZLmK-poG1prY3ITE85XicGqH_pnjCm1ANJcQn2KFtEBOlOY8srY8ibsr5ra6FdL_kUutiOyHz6buqtN1SFcxOS-5kzZHh5Ztieszy1ZHN_CvjC487Pb0b-gVeBEW2PI50gv2S3Ltp4cbVVAQlyC40idSb1XjLlWcAV9E3FKIlI-nVPMn2_VZuQcoj_AcL9sGa5Zheb7D0ZwxRLkvE6j-f68DFnd8KP8U8XTh6ag4OI8wIOkdyYgZMYhTAyKOIT7kEfbCMOEHz0wuHa5jW76FhcTfW3wDl2dFs7Tfo7xlLOOyIQfasZzGWFA0pfptrt3LX7H-KOOhQaORCMh5zJ4FkpI6TB9HhC91WywjH2iMr8HXv_qDu5-DKqer8I07JOdn1qqI9inUdPLxsFdcTNxPKX6hUAY2hviPLQLPN5KO_t4NQElhPEHMKH4D87U5kBVYzDhNFVOQEGCrbooil97uc8gGyI3q6W_9LzgIyHFmdq5xijaBmTMpt3yfhExRW5_9Cj3ZtoV5INP7oKkLTAENcwoBI3kcUcMO0AsC9ty3ZqI0p0oYDrucqF80yK0B0gO5awE72R1pk46TlziqRuK5OFNpzZv10VVdZftZcUJCm-74I8XbAB0-6YyfzPEwiewDNZ_5C19ovuTW-dPzpDFnFlZQONFclZQicN1Brk4joVR3eo9qgl2G6WVkKOiZIjkjJmpLNT0v7WOHKnndEe9lWQ2mt-Wiw5nYcYANnxQQTSJFrIxCznUnRXQwcCtWCi3gPTVxB9uElssMWXhPoQ3d9XWdeRXI09fI7V9CMDPlwf4JqVGJzCHP0QWCLNUgJQWqZGBQO9slh_40r4AsegRL5pfa-gcAABRQtV9XMKFTsc80b5lOmn5a37OJBLNPPzmDmx6roxp5jVsUEPkd7qGa_WyFFNipQFmt5OHj3mZ64YdM_6dR1sdnVXBbniCmvd4ivx6n0uOaqSuYgUnnURoUCOMgDNOgxX6nCTkvf26ysgLSN5suLcT8a22OylCGlJ11UWRu2Y0e662gBVZlfoRGfAYnBs6C3xO7Jj6kTI5ds1h_BrEitIhB2nIH2cdZ3WX29ZQemZ8SB7CBBZYBfb-4TuCd0w3BjXO7zRTPTel-yt_k-gqzVtYrllJWuRfWTvLzYIbKwkmSV-AkTjOF_dJELDHv5Itb4jUhBK1fyAc8uuJQgYgGrgNcg-bnPKDEDELNX5H-ZCWKgZaQBbE6AbfPY7b7htwrFuD1BrYcCXeFnX7sAn_jspIgJfPqj4f0C71DtMcBIk4Ywiitg_3AHjo8lTig-ZH-7Ptt2xDxVwKBXW5s4CPBkTOKqe5CC4_d4CrIVd557ihzBUEXu62gpP2EY5VJbC-8x__UdQydGib9XvACXvFqPTlhTNXdA4ljB0ZDpy8sRSjaC4SU_2Ml84yzwxXiuQmUURJOljAPvXPCNbevvBuNY=","summary":[{"type":"summary_text","text":"**Planning detailed schema and inference labeling**"},{"type":"summary_text","text":"**Emphasizing direct causes with verbose yield**"}]},{"id":"fc_0ceecce325b7fe2d016a5975ccb17481989d542837847ed3da","type":"function_call","status":"completed","arguments":"{\"type\":\"result\",\"result\":{\"data\":{\"summary\":\"## Release identity and intended v0.34 contract\\n\\n- `v0.33.0` is tag object/ref `21a46d5…`, with release commit `42d06a8…` (confirmed independently by the regression-archeology peer). The v0.34 W1 implementation then landed as:\\n  - `ad7fde8acb97b5c839316121d341375b077092df` — retirement, durable opener, retryable claims;\\n  - `2a110eed6f97d3dab96ed301d1b6d1df9a2cc002` — finished Attach is terminal/unserved, while request/reply protocols whose read side is FIN remain servable;\\n  - `84253572cd5e0cef57201ba671a8c201c6dbbdd8` — retirement is eligibility/visibility only, never physical stream teardown;\\n  - `571d6a55e9949ce1b96aef1ba00f5e007a0a9521` — ADR-0038 records that ruling.\\n- The release branch commit is `eb9fd8643921930ce21cc7ef0751a27360fc14bc` (`release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1`); the published v0.34 tag target is `b7b074c2509c1015fe341185662b4622d661f274`.\\n- ADR-0038 and requirements `REQ-HAZARD-REDISPATCH-CONTROL-STEAL`, `REQ-REDISPATCH-FINISHED-RETIRE`, `REQ-STREAM-OPENER-DURABLE`, and `REQ-DISPATCH-CLAIM-RETRY` define the intended behavior: a fresh brain must not replay terminal historical Attach streams; an active stream remains classifiable after its 4,096-chunk ring rolls; transient worker setup failures retry; legitimate same-operator active Attach reconstruction remains allowed.\\n- `CHANGELOG.md` promises that `spt update`, `spt update --restart`, and `spt daemon refresh` no longer disturb an attached remote session. That promise is accurate for the intended brain-only redispatch fix, but overstates `--restart`: `cli.rs` calls its final `Finish` leg “LETHAL,” explicitly saying it performs a full daemon restart and kills an invoking hosted PTY. `rc.rs` masks an expected broker sever with a visible, bounded 30-second reconnect loop. A silent frozen viewport is therefore not the intended `--restart` UX; a 30-second countdown banner is.\\n- The v0.34 release also shipped with malformed release-note metadata: the v0.34 heading consumed the preceding `## [0.33.0]` heading, so the workflow’s section extractor could publish both releases as one body. Commit `d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7` restored the heading after the tag. `release.yml` only checks that the requested section is nonempty; it does not validate a contiguous version ladder. The runbook now records this exact v0.34 incident and recommends a heading-ladder check.\\n\\n## What changed that can amplify a 20–30 second stall\\n\\nThe v0.34 W1 change adds no 20–30 second timer. Its dispatcher cadence is 100 ms; retries are at 500 ms and 1 second, with at most three attempts. The exact 15-second physical write deadline and 30-second RC reconnect window both predate the v0.34 delta.\\n\\nThe field trace supplied by Main materially changes the diagnosis:\\n\\n- 33 `CONN_WRITE_POISONED` incidents are attributed to `stream-subscriber` connections.\\n- Every poisoned write reaches the configured bound at approximately 15,000 ms.\\n- The corresponding `DISPATCH` failure is logged 15.1–62 seconds after write start; recent examples are 17.6, 25.4, 29.1, 30.1, 31.1, 36.6, 59.4, and 61.6 seconds.\\n- The cold generation has 4,361 `stream-sub-attach` records and 584 10-second `PUMP_PEER_FAIL` events.\\n- Stream 4834’s write began 37.354 seconds after the recent webbie controller attach and poisoned at +52.366 seconds.\\n\\nThis is an exact source match for the first 15 seconds and a plausible v0.34 amplification for the remainder:\\n\\n1. `run_dispatch_loop` enumerates every eligible broker stream every 100 ms and immediately calls `thread::spawn` once per newly claimed row. There is no semaphore, worker pool, queue bound, batch cap, or backpressure. A 4,361-row cold enumeration can therefore create thousands of simultaneous OS threads/connections.\\n2. v0.34’s durable classification performs a fresh opener RPC connection per row. `first_line` falls back to the old subscribe/replay path on **any** opener RPC error, not only an old broker’s unknown-verb response. The family worker then opens another broker connection and subscribes to the actual stream.\\n3. `serve_attach` sends `net-stream-subscribe`, then—when it has the pinned Request—immediately sends the PTY-session `attach_as` command before it begins its event-read loop. On the broker, `StreamLog::attach` installs the subscriber and synchronously writes every retained chunk while holding that stream’s `Mutex<StreamLog>`. Under a thousands-thread cold-start burst, the client worker can be descheduled or otherwise fail to drain the named pipe promptly; the broker’s Windows `WriteFileEx` fills/blocks.\\n4. `BrokerConn` enforces the 15-second absolute write deadline. `CONN_WRITE_POISONED` is emitted when the watchdog marks the operation timed out and requests `CancelIoEx`; it does **not** mean the write completed or the physical connection has severed.\\n5. After logging poison, the watchdog waits in 100 ms slices while the original `write_frame` remains in flight, repeatedly calling `CancelIoEx`. It invokes `DisconnectNamedPipe` only after `write_frame` reports completion. Meanwhile, the dispatcher’s ordinary `BrainConn::Whole` uses a direct blocking `read_frame` with no read deadline. The worker cannot return `DispatchOutcome::Failed`, and thus cannot emit the `DISPATCH` failure or release its claim, until the server connection is actually severed/EOFs.\\n6. [INFERENCE] The field’s variable 0.1–47 second post-poison tail is therefore explained by delayed Windows cancellation completion plus severe scheduler pressure from the unbounded worker burst. It is not a second intentional timeout. This is falsifiable by adding lifecycle stamps for `cancel-issued`, `write-returned/inflight-cleared`, `sever`, client EOF, and worker outcome; the long interval should occur between poison/cancel and inflight-clear/sever.\\n7. v0.33’s one-shot claim abandoned a stream after this failure. v0.34 changes every `Failed` result into a retry after 500 ms, then 1 second, up to three total attempts. Each attempt can install a fresh subscriber and synchronously replay the ring again. Thus v0.34 can turn one permanent loss into recurrent 15-second transport wedges: one deadline plus cancellation lag naturally gives 20–30 seconds; two near-minimal poisoned attempts plus the 500 ms backoff gives approximately 30.5 seconds; three attempts can approach or exceed 45 seconds. This is the strongest source-grounded v0.33→v0.34 regression relation.\\n8. Retirement does not break the loop on failure: only `DispatchOutcome::Served` triggers `net_stream_retire`. A poisoned worker returns `Failed`, so its row remains eligible for the new retry policy. Across future brain generations, the per-process claim map is empty again.\\n\\n## Causal tie to PTY/RC stalls, and its limits\\n\\n- [INFERENCE, strong if stream identity correlates] A poisoned target-side Attach subscriber directly stalls the wire→session leg: operator input/resize records cannot reach the PTY worker while synchronous replay owns that stream log and the worker connection is wedged. The terminal therefore feels frozen even though the ConPTY child and broker-held PTY output log are healthy. Stream 4834 should be decoded/classified from its pinned opener and matched to the webbie Attach; if it is that active controller’s Attach stream, the +37.354s start/+52.366s poison is direct incident evidence, not merely timing similarity.\\n- The stream lock is per QUIC stream, not the global PTY/session lock. The immediate mechanism does **not** by itself stall the ConPTY child, the broker’s session `OutputLog`, or every broker IPC connection. It predicts an affected remote attach/input stream, with unrelated PTYs and fresh control-plane probes remaining live.\\n- [INFERENCE] Thousands of simultaneous dispatcher workers/connections can add process-wide scheduler and IPC pressure, so several of approximately five active PTYs may appear to stall together. That wider effect must be measured; the lock topology alone does not prove a broker-wide stall.\\n- The separate 30-second `rc` reconnect path begins only after the viewport sees a reconnectable EOF/sever and should paint a countdown banner. During the poison→sever delay, `rc` sees no EOF and can remain silently frozen. A trace showing a silent freeze first and a countdown only after `sever` would distinguish these phases.\\n- A 10-second `PUMP_PEER_FAIL` is peer-pump failure timing, not sufficient evidence of a PTY stall. The reported 584 events indicate substantial concurrent network churn/load, but the exact causal seam for the 15-second incidents is the attributed stream-subscriber writer.\\n- A finished Attach should be classified from its pinned opener, stopped by `finished_row_is_terminal`, and retired **before** `serve_attach` subscribes. Therefore poison stream IDs that all decode to already-finished Attach rows would falsify the intended v0.34 gate or demonstrate stale/mixed broker semantics. Conversely, active Attach or other request/reply families are expected to subscribe and remain exposed.\\n\\n## Compatibility relation\\n\\n- The W1 wire changes are additive, but a v0.34 brain on an older broker cannot use durable opener or retirement verbs. `first_line` falls back to ring replay; retirement is best-effort and silently ignored until the broker cycles. The existing N-1 test checks only handshake/heartbeat steady state and does not create stream rows or exercise either new verb.\\n- This mixed-image gap contradicts the broad changelog promise for ordinary brain-only apply: the new brain can run, but the old broker cannot fully deliver v0.34 lifecycle truth. It is not the explanation for the supplied field trace if broker and brain are both confirmed v0.34, but it remains a release-contract hole.\\n- Adding broker verbs does not change the declared IPC version/resource ABI, so the update classifier can label the release `BrainOnly`; the project already exposes `broker_image`/`broker_stale` precisely because installed brain and running broker may differ. A broker refresh/full restart activates the new verbs but invokes the separate reconnect/restart tradeoff.\\n\\n## Missing coverage that allowed this regression class\\n\\n1. `redispatch.rs` D1/D1b/D2/D3 tests correctness for a handful of streams. D3 injects one failure **before setup**, so it proves claim-state arithmetic but never exercises a failed/poisoned subscriber, synchronous replay, cancellation lag, or repeated reattachment.\\n2. There is no dispatcher concurrency/burst test with thousands of retained rows and five or more simultaneous active remote Attach/PTYS. Existing `brain_resume_conn_deadlock.rs` uses six flood PTYs and checks broker heartbeat liveness, not dispatcher cold rediscovery, RC viewport gaps, input latency, or stream-subscriber poison.\\n3. `conn_blackhole_lifecycle.rs` and `brain_decouple.rs` cover controller/session subscribers, not `NetHost::StreamLog` subscribers driven through `run_dispatch_loop`. They shrink the production 15-second bound to 400 ms and assert eventual retirement/probe health, not the interval from `CONN_WRITE_POISONED` to `write_frame` completion/sever/client EOF.\\n4. `daemon_refresh_e2e.rs` proves a local hosted child PID, broker PID, and basic functionality survive refresh. It does not keep five real cross-node RC Attach streams under continuous input/output and assert maximum visible gap through a fresh dispatcher generation.\\n5. `n1_pairing.rs` proves new-brain/old-broker handshake and heartbeat only; it has zero stream rows and no opener/retire call. It cannot detect the mixed-image v0.34 semantic gap.\\n6. No release check validates consecutive changelog headings, which is why the v0.33 heading disappeared in the tagged v0.34 notes.\\n\\n## Focused fix and test design\\n\\nPriority fix design:\\n\\n1. Bound redispatch concurrency at the source. Replace per-row unbounded `thread::spawn` with a small fixed worker pool/semaphore and a bounded pending queue; batch enumeration so one cold 4,361-row table cannot instantiate 4,361 connections/threads in one sweep. Preserve one claim state per stream and never hold the claims mutex during worker I/O.\\n2. Remove synchronous socket I/O from `StreamLog::attach` while preserving replay-before-live ordering. The boring safe pattern is a per-subscriber dedicated writer/queue, analogous to the existing viewer writer: install the subscriber and enqueue the retained replay under the stream lock, release the lock, and let the writer perform framed I/O. Live append only enqueues nonblocking. Bound the queue and retain cursor/ring fallback so overload drops to resume semantics rather than blocking the QUIC read pump.\\n3. Make retry classification narrower. A pre-setup transient connect/opener race may retry; a deadline-poisoned subscriber/replay under system overload should not immediately repeat the same expensive replay blindly. At minimum, use a much longer/circuit-broken backoff for `TimedOut` and cap global retries. Do not treat every I/O failure identically.\\n4. Tighten opener fallback: fall back to ring peek only for the explicit old-broker unknown-verb response. A timeout/poison/transport error should return `Failed` under the bounded scheduler, not open another subscription/replay connection.\\n5. Instrument the Windows poison lifecycle before changing teardown safety: log CancelIoEx return/GetLastError, inflight-clear, `DisconnectNamedPipe`, handler cleanup, client EOF, dispatch family/attempt, and active/pending worker gauges. If cancel-to-completion remains long, revise the Windows abort primitive so peer read closure is prompt without releasing a frame buffer while its overlapped write is still live; the present code intentionally waits for that buffer-release handshake, so immediate sever must be designed, not guessed.\\n6. Correct the release contract: separate brain-only refresh/update survival from `--restart`’s lethal full cycle and visible RC reconnect; either require broker feature activation for W1 or state the old-broker degradation explicitly.\\n\\nFocused regression tests:\\n\\n- Windows integration test: create at least five active remote Attach sessions plus thousands of retained stale/nonterminal streams, start a fresh production dispatcher, drive unique input and autonomous output markers continuously, and assert a bounded maximum marker gap, no `CONN_WRITE_POISONED`, bounded worker/handler counts, and independent `sessions`/`net-status` probe latency.\\n- Poison lifecycle test through **NetHost stream subscription**, not session controller subscription: black-hole a stream-subscriber connection, exercise real dispatcher retry, and assert `poison → inflight-clear → sever → client EOF → DispatchOutcome` within a tight post-deadline bound. Keep a shortened-deadline structural CI variant and add a Windows release/HITL run using the real 15-second bound.\\n- Retry test: inject failure after a subscriber is installed or after its replay blocks. Assert one stream cannot produce repeated 15-second wedges and that retry does not exceed the global concurrency budget.\\n- Refresh E2E: cycle the brain while five remote RC streams remain active; assert child PID and broker output seq continue, operator input echoes, viewports continue, no old output retypes, no control steal, and no reconnect banner for a brain-only cycle.\\n- Mixed-image E2E: v0.34 brain against v0.33 broker with actual active/finished streams; explicitly assert the documented degraded behavior or require broker activation. Handshake-only is insufficient.\\n- Release lint: parse all `## [x.y.z]` headings and reject missing/duplicate/non-descending neighbors before notes extraction.\\n\\n## Fast falsification matrix for Doyle\\n\\n- Broker session output sequence advances during the visible freeze, child PID unchanged, fresh broker probes answer, and affected Attach input does not echo: local ConPTY is healthy; the per-stream subscriber/redispatch path is causal.\\n- Broker session output sequence itself stops while child should be producing, but IPC probes answer: investigate that session’s ConPTY/output drain, not RC transport.\\n- Independent fresh `sessions`/`net-status` probes also block: broker-wide IPC/scheduler pressure is present; capture dispatcher worker/handler counts and thread scheduling.\\n- `CONN_WRITE_POISONED` stream ID is the affected active Attach and poison/cancel precedes recovery: causal tie confirmed.\\n- Poison IDs are unrelated protocol families/peers and affected Attach stream has uninterrupted data: timing correlation is incidental.\\n- Long post-poison interval is between cancel and inflight-clear/sever: current watchdog mechanism confirmed. If sever occurs promptly but `DISPATCH` remains late, the delay is instead client scheduling/logging after EOF.\",\"files\":[{\"path\":\"CHANGELOG.md\",\"description\":\"v0.34 user-facing promise at lines 49–56: update/refresh, including `--restart`, should not disturb an attached remote session; current file also shows restored separate v0.33 heading.\"},{\"path\":\".git/logs/HEAD\",\"description\":\"Exact v0.34 implementation/release history: W1 commits at lines 1458–1461, release branch commit at 1465, tag-line integration at 1467, and post-tag v0.33-heading repair at 1469.\"},{\"path\":\".git/logs/refs/heads/release/v0.34.0\",\"description\":\"Release branch creation and exact counter-61 release commit `eb9fd864…`.\"},{\"path\":\"docs/adr/0038-broker-stream-lifecycle-truth.md\",\"description\":\"Canonical v0.34 design: terminal retirement, pinned opener, retryable claims, unchanged active reconstruction, and eligibility-only retirement consequences.\"},{\"path\":\"docs/REDISPATCH-TRUTH-TRIAGE.md\",\"description\":\"Field motivation (4/5 endpoints frozen twice), intended W1 scope, v0.33-boundary dispatch, and acceptance gates.\"},{\"path\":\"traceable-reqs.toml\",\"description\":\"Requirements at lines 2433–2450 for controller-steal prevention, retirement, durable opener, and retry claims; integration evidence descriptions reveal D3 is only a pre-setup synthetic transient.\"},{\"path\":\"crates/spt-daemon/src/dispatch.rs\",\"description\":\"Core regression seam: 100 ms poll, 500 ms/1 s retry with max three attempts (lines 77–230); unbounded per-row thread spawning (299–361); opener-on-new-connection with broad error fallback to replay subscription (391–452); Attach terminal gate and serve path (479 onward).\"},{\"path\":\"crates/spt-daemon/src/nethost.rs\",\"description\":\"`StreamLog::append/finish/attach` performs subscriber writes synchronously under the per-stream log lock (roughly 284–351); subscribe calls `log.lock().attach` (1676–1693); stream enumeration excludes retired rows and retirement only clears eligibility/ring (1518–1599).\"},{\"path\":\"crates/spt-daemon/src/conn.rs\",\"description\":\"Windows bounded connection writer: 15-second deadline ownership, poison/cancel, 100 ms recancel loop, and sever only after inflight write completion (roughly 179–399 and 459–604). Explains why poison log precedes dispatch failure by a variable interval.\"},{\"path\":\"crates/spt-daemon/src/brain.rs\",\"description\":\"Ordinary `BrainConn::Whole` clients do direct blocking reads with no deadline; only pump Split connections use channel deadlines (199–333 and 1734–1778). Dispatcher worker therefore awaits actual sever/EOF after poison.\"},{\"path\":\"crates/spt-daemon/src/attach.rs\",\"description\":\"Target-side Attach flow: net-stream subscription, second connection for output forwarding, pinned Request handling, then session attach before the event read loop (279–443). Establishes the stream-subscriber/input-leg causal boundary.\"},{\"path\":\"crates/spt-daemon/src/broker.rs\",\"description\":\"Each broker client gets one bounded `BrokerConn`; `handle_conn` synchronously dispatches `KIND_NET_STREAM_SUBSCRIBE` and cleans stream subscriptions only after receive EOF (roughly 2980–3230).\"},{\"path\":\"crates/spt/src/cli.rs\",\"description\":\"Update planning explicitly defines `Finish`/`--restart` as lethal full-daemon restart that kills an invoking hosted PTY, while ordinary FetchApply is brain-only (5038–5098).\"},{\"path\":\"crates/spt/src/rc.rs\",\"description\":\"Visible RC reconnect loop and exact 30-second window with one-second pauses (1545–1665); distinguishes intended broker-bounce UX from silent pre-sever poison stall.\"},{\"path\":\"crates/spt-daemon/tests/redispatch.rs\",\"description\":\"W1 D1/D1b/D2/D3 integration coverage; D3 injects a single transient before setup, so it never covers subscriber replay poison or recurring retry stalls.\"},{\"path\":\"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs\",\"description\":\"Six-session flood coverage checks heartbeat liveness, not cold dispatcher fan-out, RC input/output gaps, or stream-subscriber timeout behavior.\"},{\"path\":\"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs\",\"description\":\"Windows blackhole coverage for session controller connections with shortened deadline; not a NetHost stream subscriber through the dispatcher.\"},{\"path\":\"crates/spt/tests/daemon_refresh_e2e.rs\",\"description\":\"Proves local broker/PTY child survival through a brain cycle but has no five-way remote Attach/RC continuity assertion.\"},{\"path\":\"crates/spt/tests/n1_pairing.rs\",\"description\":\"New-brain/old-broker N-1 test covers handshake and heartbeat only; no stream opener, retirement, replay, or active session row.\"},{\"path\":\".github/workflows/release.yml\",\"description\":\"Extracts requested CHANGELOG section and only rejects empty notes; no version-ladder/neighbor validation, allowing the v0.34/v0.33 heading accident.\"},{\"path\":\"docs/RELEASE-RUNBOOK.md\",\"description\":\"Records that v0.34 consumed the v0.33 heading and recommends a contiguous heading grep; currently a manual self-check/candidate CI lint.\"}],\"architecture\":\"A remote controller opens a QUIC Attach stream. The stable broker owns the QUIC stream table and ConPTY/session handles; the replaceable brain’s dispatcher enumerates peer-initiated rows and creates a worker per claim. A worker classifies from the broker-pinned opener, subscribes a local broker IPC connection to the Attach stream, then attaches that same brain connection to the broker-held PTY session; a second broker connection forwards session output back onto QUIC. In v0.34, claim failures retry up to three times and successful exchanges retire from future enumeration. The critical pressure path is `fresh dispatcher → unbounded workers/connections → synchronous StreamLog replay under per-stream lock → Windows named-pipe write blocks → 15s BrokerConn poison/cancel → delayed inflight completion/sever → blocking Whole-brain read finally EOFs → DispatchOutcome::Failed → v0.34 retry`. This is a local RC/stream transport stall when confined to one Attach stream, not a ConPTY output stall. It becomes a broader IPC/scheduler stall only through the thousands-worker fan-out. A full broker restart is a separate path: it deliberately kills hosted PTYs and `rc` uses a visible 30-second reconnect window; a brain-only refresh is supposed to preserve broker, children, streams, and viewport continuously.\"}}}","call_id":"call_Cxv1BnOwpBApkWbzuozfrjun","name":"yield"}]},"duration":99595.35429999977,"ttft":1342.9308000057936,"contextSnapshot":{"promptTokens":94440,"nonMessageTokens":15420}}}
{"type":"custom","customType":"tool_execution_start","data":{"toolCallId":"call_Cxv1BnOwpBApkWbzuozfrjun|fc_0ceecce325b7fe2d016a5975ccb17481989d542837847ed3da","toolName":"yield","startedAt":"2026-07-17T00:24:11.468Z"},"id":"5b555ff3","parentId":"b5721876","timestamp":"2026-07-17T00:24:11.468Z"}
{"type":"message","id":"b612e527","parentId":"5b555ff3","timestamp":"2026-07-17T00:24:11.469Z","message":{"role":"toolResult","toolCallId":"call_Cxv1BnOwpBApkWbzuozfrjun|fc_0ceecce325b7fe2d016a5975ccb17481989d542837847ed3da","toolName":"yield","content":[{"type":"text","text":"Result submitted."}],"details":{"data":{"summary":"## Release identity and intended v0.34 contract\n\n- `v0.33.0` is tag object/ref `21a46d5…`, with release commit `42d06a8…` (confirmed independently by the regression-archeology peer). The v0.34 W1 implementation then landed as:\n  - `ad7fde8acb97b5c839316121d341375b077092df` — retirement, durable opener, retryable claims;\n  - `2a110eed6f97d3dab96ed301d1b6d1df9a2cc002` — finished Attach is terminal/unserved, while request/reply protocols whose read side is FIN remain servable;\n  - `84253572cd5e0cef57201ba671a8c201c6dbbdd8` — retirement is eligibility/visibility only, never physical stream teardown;\n  - `571d6a55e9949ce1b96aef1ba00f5e007a0a9521` — ADR-0038 records that ruling.\n- The release branch commit is `eb9fd8643921930ce21cc7ef0751a27360fc14bc` (`release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1`); the published v0.34 tag target is `b7b074c2509c1015fe341185662b4622d661f274`.\n- ADR-0038 and requirements `REQ-HAZARD-REDISPATCH-CONTROL-STEAL`, `REQ-REDISPATCH-FINISHED-RETIRE`, `REQ-STREAM-OPENER-DURABLE`, and `REQ-DISPATCH-CLAIM-RETRY` define the intended behavior: a fresh brain must not replay terminal historical Attach streams; an active stream remains classifiable after its 4,096-chunk ring rolls; transient worker setup failures retry; legitimate same-operator active Attach reconstruction remains allowed.\n- `CHANGELOG.md` promises that `spt update`, `spt update --restart`, and `spt daemon refresh` no longer disturb an attached remote session. That promise is accurate for the intended brain-only redispatch fix, but overstates `--restart`: `cli.rs` calls its final `Finish` leg “LETHAL,” explicitly saying it performs a full daemon restart and kills an invoking hosted PTY. `rc.rs` masks an expected broker sever with a visible, bounded 30-second reconnect loop. A silent frozen viewport is therefore not the intended `--restart` UX; a 30-second countdown banner is.\n- The v0.34 release also shipped with malformed release-note metadata: the v0.34 heading consumed the preceding `## [0.33.0]` heading, so the workflow’s section extractor could publish both releases as one body. Commit `d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7` restored the heading after the tag. `release.yml` only checks that the requested section is nonempty; it does not validate a contiguous version ladder. The runbook now records this exact v0.34 incident and recommends a heading-ladder check.\n\n## What changed that can amplify a 20–30 second stall\n\nThe v0.34 W1 change adds no 20–30 second timer. Its dispatcher cadence is 100 ms; retries are at 500 ms and 1 second, with at most three attempts. The exact 15-second physical write deadline and 30-second RC reconnect window both predate the v0.34 delta.\n\nThe field trace supplied by Main materially changes the diagnosis:\n\n- 33 `CONN_WRITE_POISONED` incidents are attributed to `stream-subscriber` connections.\n- Every poisoned write reaches the configured bound at approximately 15,000 ms.\n- The corresponding `DISPATCH` failure is logged 15.1–62 seconds after write start; recent examples are 17.6, 25.4, 29.1, 30.1, 31.1, 36.6, 59.4, and 61.6 seconds.\n- The cold generation has 4,361 `stream-sub-attach` records and 584 10-second `PUMP_PEER_FAIL` events.\n- Stream 4834’s write began 37.354 seconds after the recent webbie controller attach and poisoned at +52.366 seconds.\n\nThis is an exact source match for the first 15 seconds and a plausible v0.34 amplification for the remainder:\n\n1. `run_dispatch_loop` enumerates every eligible broker stream every 100 ms and immediately calls `thread::spawn` once per newly claimed row. There is no semaphore, worker pool, queue bound, batch cap, or backpressure. A 4,361-row cold enumeration can therefore create thousands of simultaneous OS threads/connections.\n2. v0.34’s durable classification performs a fresh opener RPC connection per row. `first_line` falls back to the old subscribe/replay path on **any** opener RPC error, not only an old broker’s unknown-verb response. The family worker then opens another broker connection and subscribes to the actual stream.\n3. `serve_attach` sends `net-stream-subscribe`, then—when it has the pinned Request—immediately sends the PTY-session `attach_as` command before it begins its event-read loop. On the broker, `StreamLog::attach` installs the subscriber and synchronously writes every retained chunk while holding that stream’s `Mutex<StreamLog>`. Under a thousands-thread cold-start burst, the client worker can be descheduled or otherwise fail to drain the named pipe promptly; the broker’s Windows `WriteFileEx` fills/blocks.\n4. `BrokerConn` enforces the 15-second absolute write deadline. `CONN_WRITE_POISONED` is emitted when the watchdog marks the operation timed out and requests `CancelIoEx`; it does **not** mean the write completed or the physical connection has severed.\n5. After logging poison, the watchdog waits in 100 ms slices while the original `write_frame` remains in flight, repeatedly calling `CancelIoEx`. It invokes `DisconnectNamedPipe` only after `write_frame` reports completion. Meanwhile, the dispatcher’s ordinary `BrainConn::Whole` uses a direct blocking `read_frame` with no read deadline. The worker cannot return `DispatchOutcome::Failed`, and thus cannot emit the `DISPATCH` failure or release its claim, until the server connection is actually severed/EOFs.\n6. [INFERENCE] The field’s variable 0.1–47 second post-poison tail is therefore explained by delayed Windows cancellation completion plus severe scheduler pressure from the unbounded worker burst. It is not a second intentional timeout. This is falsifiable by adding lifecycle stamps for `cancel-issued`, `write-returned/inflight-cleared`, `sever`, client EOF, and worker outcome; the long interval should occur between poison/cancel and inflight-clear/sever.\n7. v0.33’s one-shot claim abandoned a stream after this failure. v0.34 changes every `Failed` result into a retry after 500 ms, then 1 second, up to three total attempts. Each attempt can install a fresh subscriber and synchronously replay the ring again. Thus v0.34 can turn one permanent loss into recurrent 15-second transport wedges: one deadline plus cancellation lag naturally gives 20–30 seconds; two near-minimal poisoned attempts plus the 500 ms backoff gives approximately 30.5 seconds; three attempts can approach or exceed 45 seconds. This is the strongest source-grounded v0.33→v0.34 regression relation.\n8. Retirement does not break the loop on failure: only `DispatchOutcome::Served` triggers `net_stream_retire`. A poisoned worker returns `Failed`, so its row remains eligible for the new retry policy. Across future brain generations, the per-process claim map is empty again.\n\n## Causal tie to PTY/RC stalls, and its limits\n\n- [INFERENCE, strong if stream identity correlates] A poisoned target-side Attach subscriber directly stalls the wire→session leg: operator input/resize records cannot reach the PTY worker while synchronous replay owns that stream log and the worker connection is wedged. The terminal therefore feels frozen even though the ConPTY child and broker-held PTY output log are healthy. Stream 4834 should be decoded/classified from its pinned opener and matched to the webbie Attach; if it is that active controller’s Attach stream, the +37.354s start/+52.366s poison is direct incident evidence, not merely timing similarity.\n- The stream lock is per QUIC stream, not the global PTY/session lock. The immediate mechanism does **not** by itself stall the ConPTY child, the broker’s session `OutputLog`, or every broker IPC connection. It predicts an affected remote attach/input stream, with unrelated PTYs and fresh control-plane probes remaining live.\n- [INFERENCE] Thousands of simultaneous dispatcher workers/connections can add process-wide scheduler and IPC pressure, so several of approximately five active PTYs may appear to stall together. That wider effect must be measured; the lock topology alone does not prove a broker-wide stall.\n- The separate 30-second `rc` reconnect path begins only after the viewport sees a reconnectable EOF/sever and should paint a countdown banner. During the poison→sever delay, `rc` sees no EOF and can remain silently frozen. A trace showing a silent freeze first and a countdown only after `sever` would distinguish these phases.\n- A 10-second `PUMP_PEER_FAIL` is peer-pump failure timing, not sufficient evidence of a PTY stall. The reported 584 events indicate substantial concurrent network churn/load, but the exact causal seam for the 15-second incidents is the attributed stream-subscriber writer.\n- A finished Attach should be classified from its pinned opener, stopped by `finished_row_is_terminal`, and retired **before** `serve_attach` subscribes. Therefore poison stream IDs that all decode to already-finished Attach rows would falsify the intended v0.34 gate or demonstrate stale/mixed broker semantics. Conversely, active Attach or other request/reply families are expected to subscribe and remain exposed.\n\n## Compatibility relation\n\n- The W1 wire changes are additive, but a v0.34 brain on an older broker cannot use durable opener or retirement verbs. `first_line` falls back to ring replay; retirement is best-effort and silently ignored until the broker cycles. The existing N-1 test checks only handshake/heartbeat steady state and does not create stream rows or exercise either new verb.\n- This mixed-image gap contradicts the broad changelog promise for ordinary brain-only apply: the new brain can run, but the old broker cannot fully deliver v0.34 lifecycle truth. It is not the explanation for the supplied field trace if broker and brain are both confirmed v0.34, but it remains a release-contract hole.\n- Adding broker verbs does not change the declared IPC version/resource ABI, so the update classifier can label the release `BrainOnly`; the project already exposes `broker_image`/`broker_stale` precisely because installed brain and running broker may differ. A broker refresh/full restart activates the new verbs but invokes the separate reconnect/restart tradeoff.\n\n## Missing coverage that allowed this regression class\n\n1. `redispatch.rs` D1/D1b/D2/D3 tests correctness for a handful of streams. D3 injects one failure **before setup**, so it proves claim-state arithmetic but never exercises a failed/poisoned subscriber, synchronous replay, cancellation lag, or repeated reattachment.\n2. There is no dispatcher concurrency/burst test with thousands of retained rows and five or more simultaneous active remote Attach/PTYS. Existing `brain_resume_conn_deadlock.rs` uses six flood PTYs and checks broker heartbeat liveness, not dispatcher cold rediscovery, RC viewport gaps, input latency, or stream-subscriber poison.\n3. `conn_blackhole_lifecycle.rs` and `brain_decouple.rs` cover controller/session subscribers, not `NetHost::StreamLog` subscribers driven through `run_dispatch_loop`. They shrink the production 15-second bound to 400 ms and assert eventual retirement/probe health, not the interval from `CONN_WRITE_POISONED` to `write_frame` completion/sever/client EOF.\n4. `daemon_refresh_e2e.rs` proves a local hosted child PID, broker PID, and basic functionality survive refresh. It does not keep five real cross-node RC Attach streams under continuous input/output and assert maximum visible gap through a fresh dispatcher generation.\n5. `n1_pairing.rs` proves new-brain/old-broker handshake and heartbeat only; it has zero stream rows and no opener/retire call. It cannot detect the mixed-image v0.34 semantic gap.\n6. No release check validates consecutive changelog headings, which is why the v0.33 heading disappeared in the tagged v0.34 notes.\n\n## Focused fix and test design\n\nPriority fix design:\n\n1. Bound redispatch concurrency at the source. Replace per-row unbounded `thread::spawn` with a small fixed worker pool/semaphore and a bounded pending queue; batch enumeration so one cold 4,361-row table cannot instantiate 4,361 connections/threads in one sweep. Preserve one claim state per stream and never hold the claims mutex during worker I/O.\n2. Remove synchronous socket I/O from `StreamLog::attach` while preserving replay-before-live ordering. The boring safe pattern is a per-subscriber dedicated writer/queue, analogous to the existing viewer writer: install the subscriber and enqueue the retained replay under the stream lock, release the lock, and let the writer perform framed I/O. Live append only enqueues nonblocking. Bound the queue and retain cursor/ring fallback so overload drops to resume semantics rather than blocking the QUIC read pump.\n3. Make retry classification narrower. A pre-setup transient connect/opener race may retry; a deadline-poisoned subscriber/replay under system overload should not immediately repeat the same expensive replay blindly. At minimum, use a much longer/circuit-broken backoff for `TimedOut` and cap global retries. Do not treat every I/O failure identically.\n4. Tighten opener fallback: fall back to ring peek only for the explicit old-broker unknown-verb response. A timeout/poison/transport error should return `Failed` under the bounded scheduler, not open another subscription/replay connection.\n5. Instrument the Windows poison lifecycle before changing teardown safety: log CancelIoEx return/GetLastError, inflight-clear, `DisconnectNamedPipe`, handler cleanup, client EOF, dispatch family/attempt, and active/pending worker gauges. If cancel-to-completion remains long, revise the Windows abort primitive so peer read closure is prompt without releasing a frame buffer while its overlapped write is still live; the present code intentionally waits for that buffer-release handshake, so immediate sever must be designed, not guessed.\n6. Correct the release contract: separate brain-only refresh/update survival from `--restart`’s lethal full cycle and visible RC reconnect; either require broker feature activation for W1 or state the old-broker degradation explicitly.\n\nFocused regression tests:\n\n- Windows integration test: create at least five active remote Attach sessions plus thousands of retained stale/nonterminal streams, start a fresh production dispatcher, drive unique input and autonomous output markers continuously, and assert a bounded maximum marker gap, no `CONN_WRITE_POISONED`, bounded worker/handler counts, and independent `sessions`/`net-status` probe latency.\n- Poison lifecycle test through **NetHost stream subscription**, not session controller subscription: black-hole a stream-subscriber connection, exercise real dispatcher retry, and assert `poison → inflight-clear → sever → client EOF → DispatchOutcome` within a tight post-deadline bound. Keep a shortened-deadline structural CI variant and add a Windows release/HITL run using the real 15-second bound.\n- Retry test: inject failure after a subscriber is installed or after its replay blocks. Assert one stream cannot produce repeated 15-second wedges and that retry does not exceed the global concurrency budget.\n- Refresh E2E: cycle the brain while five remote RC streams remain active; assert child PID and broker output seq continue, operator input echoes, viewports continue, no old output retypes, no control steal, and no reconnect banner for a brain-only cycle.\n- Mixed-image E2E: v0.34 brain against v0.33 broker with actual active/finished streams; explicitly assert the documented degraded behavior or require broker activation. Handshake-only is insufficient.\n- Release lint: parse all `## [x.y.z]` headings and reject missing/duplicate/non-descending neighbors before notes extraction.\n\n## Fast falsification matrix for Doyle\n\n- Broker session output sequence advances during the visible freeze, child PID unchanged, fresh broker probes answer, and affected Attach input does not echo: local ConPTY is healthy; the per-stream subscriber/redispatch path is causal.\n- Broker session output sequence itself stops while child should be producing, but IPC probes answer: investigate that session’s ConPTY/output drain, not RC transport.\n- Independent fresh `sessions`/`net-status` probes also block: broker-wide IPC/scheduler pressure is present; capture dispatcher worker/handler counts and thread scheduling.\n- `CONN_WRITE_POISONED` stream ID is the affected active Attach and poison/cancel precedes recovery: causal tie confirmed.\n- Poison IDs are unrelated protocol families/peers and affected Attach stream has uninterrupted data: timing correlation is incidental.\n- Long post-poison interval is between cancel and inflight-clear/sever: current watchdog mechanism confirmed. If sever occurs promptly but `DISPATCH` remains late, the delay is instead client scheduling/logging after EOF.","files":[{"path":"CHANGELOG.md","description":"v0.34 user-facing promise at lines 49–56: update/refresh, including `--restart`, should not disturb an attached remote session; current file also shows restored separate v0.33 heading."},{"path":".git/logs/HEAD","description":"Exact v0.34 implementation/release history: W1 commits at lines 1458–1461, release branch commit at 1465, tag-line integration at 1467, and post-tag v0.33-heading repair at 1469."},{"path":".git/logs/refs/heads/release/v0.34.0","description":"Release branch creation and exact counter-61 release commit `eb9fd864…`."},{"path":"docs/adr/0038-broker-stream-lifecycle-truth.md","description":"Canonical v0.34 design: terminal retirement, pinned opener, retryable claims, unchanged active reconstruction, and eligibility-only retirement consequences."},{"path":"docs/REDISPATCH-TRUTH-TRIAGE.md","description":"Field motivation (4/5 endpoints frozen twice), intended W1 scope, v0.33-boundary dispatch, and acceptance gates."},{"path":"traceable-reqs.toml","description":"Requirements at lines 2433–2450 for controller-steal prevention, retirement, durable opener, and retry claims; integration evidence descriptions reveal D3 is only a pre-setup synthetic transient."},{"path":"crates/spt-daemon/src/dispatch.rs","description":"Core regression seam: 100 ms poll, 500 ms/1 s retry with max three attempts (lines 77–230); unbounded per-row thread spawning (299–361); opener-on-new-connection with broad error fallback to replay subscription (391–452); Attach terminal gate and serve path (479 onward)."},{"path":"crates/spt-daemon/src/nethost.rs","description":"`StreamLog::append/finish/attach` performs subscriber writes synchronously under the per-stream log lock (roughly 284–351); subscribe calls `log.lock().attach` (1676–1693); stream enumeration excludes retired rows and retirement only clears eligibility/ring (1518–1599)."},{"path":"crates/spt-daemon/src/conn.rs","description":"Windows bounded connection writer: 15-second deadline ownership, poison/cancel, 100 ms recancel loop, and sever only after inflight write completion (roughly 179–399 and 459–604). Explains why poison log precedes dispatch failure by a variable interval."},{"path":"crates/spt-daemon/src/brain.rs","description":"Ordinary `BrainConn::Whole` clients do direct blocking reads with no deadline; only pump Split connections use channel deadlines (199–333 and 1734–1778). Dispatcher worker therefore awaits actual sever/EOF after poison."},{"path":"crates/spt-daemon/src/attach.rs","description":"Target-side Attach flow: net-stream subscription, second connection for output forwarding, pinned Request handling, then session attach before the event read loop (279–443). Establishes the stream-subscriber/input-leg causal boundary."},{"path":"crates/spt-daemon/src/broker.rs","description":"Each broker client gets one bounded `BrokerConn`; `handle_conn` synchronously dispatches `KIND_NET_STREAM_SUBSCRIBE` and cleans stream subscriptions only after receive EOF (roughly 2980–3230)."},{"path":"crates/spt/src/cli.rs","description":"Update planning explicitly defines `Finish`/`--restart` as lethal full-daemon restart that kills an invoking hosted PTY, while ordinary FetchApply is brain-only (5038–5098)."},{"path":"crates/spt/src/rc.rs","description":"Visible RC reconnect loop and exact 30-second window with one-second pauses (1545–1665); distinguishes intended broker-bounce UX from silent pre-sever poison stall."},{"path":"crates/spt-daemon/tests/redispatch.rs","description":"W1 D1/D1b/D2/D3 integration coverage; D3 injects a single transient before setup, so it never covers subscriber replay poison or recurring retry stalls."},{"path":"crates/spt-daemon/tests/brain_resume_conn_deadlock.rs","description":"Six-session flood coverage checks heartbeat liveness, not cold dispatcher fan-out, RC input/output gaps, or stream-subscriber timeout behavior."},{"path":"crates/spt-daemon/tests/conn_blackhole_lifecycle.rs","description":"Windows blackhole coverage for session controller connections with shortened deadline; not a NetHost stream subscriber through the dispatcher."},{"path":"crates/spt/tests/daemon_refresh_e2e.rs","description":"Proves local broker/PTY child survival through a brain cycle but has no five-way remote Attach/RC continuity assertion."},{"path":"crates/spt/tests/n1_pairing.rs","description":"New-brain/old-broker N-1 test covers handshake and heartbeat only; no stream opener, retirement, replay, or active session row."},{"path":".github/workflows/release.yml","description":"Extracts requested CHANGELOG section and only rejects empty notes; no version-ladder/neighbor validation, allowing the v0.34/v0.33 heading accident."},{"path":"docs/RELEASE-RUNBOOK.md","description":"Records that v0.34 consumed the v0.33 heading and recommends a contiguous heading grep; currently a manual self-check/candidate CI lint."}],"architecture":"A remote controller opens a QUIC Attach stream. The stable broker owns the QUIC stream table and ConPTY/session handles; the replaceable brain’s dispatcher enumerates peer-initiated rows and creates a worker per claim. A worker classifies from the broker-pinned opener, subscribes a local broker IPC connection to the Attach stream, then attaches that same brain connection to the broker-held PTY session; a second broker connection forwards session output back onto QUIC. In v0.34, claim failures retry up to three times and successful exchanges retire from future enumeration. The critical pressure path is `fresh dispatcher → unbounded workers/connections → synchronous StreamLog replay under per-stream lock → Windows named-pipe write blocks → 15s BrokerConn poison/cancel → delayed inflight completion/sever → blocking Whole-brain read finally EOFs → DispatchOutcome::Failed → v0.34 retry`. This is a local RC/stream transport stall when confined to one Attach stream, not a ConPTY output stall. It becomes a broader IPC/scheduler stall only through the thousands-worker fan-out. A full broker restart is a separate path: it deliberately kills hosted PTYs and `rc` uses a visible 30-second reconnect window; a brain-only refresh is supposed to preserve broker, children, streams, and viewport continuously."},"status":"success","type":"result"},"isError":false,"timestamp":1784247851468}}
{"type":"message","id":"45daf53e","parentId":"b612e527","timestamp":"2026-07-17T00:24:11.491Z","message":{"role":"assistant","content":[],"api":"openai-codex-responses","provider":"openai-codex","model":"gpt-5.6-sol","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"aborted","errorMessage":"Request was aborted","errorId":134221824,"timestamp":1784247851487}}
{"type":"custom","customType":"session_exit","data":{"reason":"dispose","kind":"normal","recordedAt":"2026-07-17T00:31:11.551Z"},"id":"b8a7a60c","parentId":"45daf53e","timestamp":"2026-07-17T00:31:11.551Z"}
